Skip to content

Conversation

RussKie
Copy link
Member

@RussKie RussKie commented Aug 12, 2023

Proposed changes

Introduce the concept of a service container, which gets initialised at the very start of the app. IGitUICommands implements IServiceProvider, which means the service container is available anywhere where an instance of GitUICommands is.
The service container is plumbed in, and MEF removed. However, nothing else has changed at this stage, and further enhancements will be coming as follow up work.

Merge strategy

❗ Don't squash.


✒️ I contribute this code under The Developer Certificate of Origin.

@ghost ghost assigned RussKie Aug 12, 2023
@RussKie RussKie force-pushed the dependency_injection branch 9 times, most recently from 42ab47f to bf88dfa Compare August 30, 2023 01:55
@RussKie RussKie closed this Aug 30, 2023
@RussKie RussKie reopened this Aug 30, 2023
@RussKie RussKie force-pushed the dependency_injection branch from bf88dfa to 8d78eed Compare August 30, 2023 08:07
@RussKie RussKie closed this Sep 4, 2023
@RussKie RussKie reopened this Sep 4, 2023
@RussKie RussKie force-pushed the dependency_injection branch from e28381b to aae8ef3 Compare September 4, 2023 10:34
@RussKie RussKie force-pushed the dependency_injection branch from 3a4edff to 6f881e8 Compare September 14, 2023 13:43
@RussKie RussKie marked this pull request as ready for review September 14, 2023 13:46
@RussKie RussKie force-pushed the dependency_injection branch from 6f881e8 to 7363e9d Compare September 15, 2023 03:28
@RussKie RussKie force-pushed the dependency_injection branch from 1555c29 to fa71764 Compare September 19, 2023 13:35
@RussKie
Copy link
Member Author

RussKie commented Sep 20, 2023

I'd like to have this merged. If no objections/feedback received, I'll merge this by the end of the week.

Copy link
Member

@mstv mstv left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems to work. I do not have principle objections.
The review of the first commit and of Program.cs have raised questions which should be clarified before the complete review.

Copy link
Member

@gerhardol gerhardol left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure what the benefit is with the change.
But I trust the change and have no objections to merge.
Have only review by "pattern matching", i.e. uniform changes. Not run

@RussKie
Copy link
Member Author

RussKie commented Sep 21, 2023

Not sure what the benefit is with the change.

This is the first step to using dependency injection throughout the application - i.e., avoid newing up instances inplace, and rather create a dependency tree upfront. This will have perf benefits and will make testing much easier.
This will also allow us to make the implementations much more decoupled and modular.

Copy link
Member

@mstv mstv left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I took the liberty to fix some comments myself. Could do for ScriptOptionsParser, too.

What do you think about the strict compostion in 09d143c?
I think it is clearer without loss of convenience.

@RussKie RussKie force-pushed the dependency_injection branch from 0a02a1b to 15f19ec Compare September 24, 2023 07:24
@RussKie
Copy link
Member Author

RussKie commented Sep 24, 2023

What do you think about the strict compostion in 09d143c?
I think it is clearer without loss of convenience.

I've gone through several iterations of the API, and I didn't want to expose the SP publicly. To me it feels like an implementation detail which would unnecessarily burden the API surface. That said, I don't consider the API locked, and it's possible/likely a number of API will change as we utilise the DI more.

@RussKie RussKie force-pushed the dependency_injection branch from 15f19ec to d31ae17 Compare September 24, 2023 07:47
@mstv
Copy link
Member

mstv commented Sep 24, 2023

I've gone through several iterations of the API, and I didn't want to expose the SP publicly.

The SP interface is publicly exposed by public interface IGitUICommands : IServiceProvider.
public IServiceProvider ServiceProvider { get; init; } does not expose more than the direct interface implementation.

Public inheritance / interface implementation reads as "UICommands is a/the SP". UICommands is not the SP in the strict sense, it just holds it and makes it accessible.

(Since serviceProvider: UICommands is being eliminated by the factory methods With...(), this discussion is getting minor relevance.)

@RussKie RussKie force-pushed the dependency_injection branch from f5ffdb9 to 9078788 Compare September 25, 2023 02:45
@RussKie RussKie force-pushed the dependency_injection branch from 9078788 to 7ecedd9 Compare September 25, 2023 06:24
@RussKie
Copy link
Member Author

RussKie commented Sep 25, 2023

I started with passing IServiceProvider to every form, since it kind of felt it was a responsibility of a UI component to know how to resolve its dependencies. However, it quickly spiralled out of control, and even though I saw that change through to a compilable state, there were way too many changes. Also, each UI control (well, almost) had a reference to IGitUICommands, which in a way behaves as a "controller" for UI components. So, to minimise the change surface I decided to extend that interface with IServiceProvider.

Exposing IServiceProvider as a property of IGitUICommands doesn't feel right to me, it feels as a leaky abstraction.

I dislike the current implementation with the inheritance and (re-)assignment of IGitUICommandsSource. In the long term I'd like to replace that with IServiceProvider, from which a current instance of IGitUICommands or IGitModule can be resolved. That would allow us to modularise and componentise the app.

@mstv mstv force-pushed the dependency_injection branch from 0c81f0a to 89b620e Compare September 25, 2023 18:59
Comment on lines +61 to +63
serviceContainer.AddService(Substitute.For<IAppTitleGenerator>());
serviceContainer.AddService(Substitute.For<IWindowsJumpListManager>());
serviceContainer.AddService(Substitute.For<ResourceManager.ILinkFactory>());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed, this works without specifying the interface type .AddService<IType>(...).

/// Creates a new instance of <see cref="GitUICommands"/> for a git repository specified by <paramref name="module"/>.
/// Creates a new instance of <see cref="GitUICommands"/> for a git repository specified by <paramref name="module"/>.
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've done this on purpose. The double space makes it easier to read comments. We use this convention in a few .NET repos, notably in dotnet/winforms.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The double space makes it easier to read comments.

This is pretty new - and done inconsistently in this PR (at least in Plugins/GitUIPluginInterfaces/ServiceProviderExtensions.cs). I do not have strong opinion on it.

Comment on lines 332 to 333
return _commitDataBodyRenderer?.Render(data, showRevisionsAsLinks: false) ?? string.Empty;
return _commitDataBodyRenderer.Render(data, showRevisionsAsLinks: false);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not 100% convinced that this is safe. The data loading in this control is async, and without any cancelations. So, it's quite possible to get in a state where data is being loaded, and at the same time a repo is being unloaded which sets these variables to null.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are right, I stopped in the middle of a right movement.

@@ -420,11 +427,9 @@ async Task LoadLinksForRevisionAsync(GitRevision revision, DistributedSettings s

string GetLinksForRevision(DistributedSettings settings)
{
IEnumerable<ExternalLink> links = _gitRevisionExternalLinksParser.Parse(revision, settings);
cancellationToken.ThrowIfCancellationRequested();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved down on purpose because the caller has checked for cancellation right before calling this function.

@RussKie RussKie force-pushed the dependency_injection branch from 0b48cb5 to 3d98756 Compare September 28, 2023 03:19
@RussKie RussKie closed this Sep 28, 2023
@RussKie RussKie reopened this Sep 28, 2023
@RussKie RussKie merged commit d725f7f into gitextensions:master Sep 28, 2023
@ghost ghost added this to the vNext milestone Sep 28, 2023
@RussKie RussKie deleted the dependency_injection branch September 28, 2023 07:50
@RussKie
Copy link
Member Author

RussKie commented Sep 28, 2023

Thank you for the reviews

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants