Skip to content
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

refactor: migrate away from MEF to Dependency Injection #412

Merged
merged 17 commits into from
Feb 14, 2023

Conversation

JamieMagee
Copy link
Member

This is a quite a large PR, that breaks down all the steps required to close #400. I strongly recommend reviewing this commit-by-commit and reading the commit message.

1. refactor: add constructors to injected classes

Currently, Managed Extensibility Framework (MEF) does property injection. In migrating to .NET's own Dependency Injection framework Microsoft.Extensions.DependencyInjection we need to migrate to constructor injection. This change adds 2 constructors for every class that has properties injected into it:

  • A parameterless constructor (for MEF)
  • A constructor with parameters for all the required dependencies (for .NET DI)

This is an intermediate step that allows MEF to work, while work continues on the migration to MEF.

2. test: add DetectorTestUtilityBuilder

This is the equivalent of DetectorTestUtilityCreator and DetectorTestUtility, but for .NET DI.

3. test: migrate component detector tests to use `DetectorTestUtilityBuilder`

This change migrates all of the component detector tests to us DetectorTestUtilityBuilder, based on .NET DI, instead of DetectorTestUtility, based on MEF.

This also adds a new base class for all component detector tests called BaseDetectorTest which constructs the DetectorTestUtilityBuilder in its constructor.

4. test: delete DetectorTestUtility and DetectorTestUtilityCreator

No longer required after all component detector tests have been migrated to use DetectorTestUtilityBuilder instead

5. test: use dependency injection constructor for service tests

This change uses the constructor that has parameters for all required properties of a service, instead of using object initializers (which actually use the parameterless constructor)

6. feat: add all services to AddComponentDetection

This change adds all of the services that will be managed by .NET's dependency injection framework to the AddComponentDetection method. This allows you easily configure all the services that Component Detection requires

var serviceProvider = new ServiceCollection()
  .AddComponentDetection()
  .BuildServiceProvider();
var orchestrator = serviceProvider.GetRequiredService<Orchestrator>();
var result = await orchestrator.LoadAsync(args);
7. refactor: use AddComponentDetection to setup services

This is one of the final changes in migrating away from Managed Extensibility Framework (MEF) and to .NET's Dependency Injection (DI).

These changes include:

  • Deleting classes that are no longer required for handling property injection:
    • IDetectorDependencies and DetectorDependencies which held references to all common services
    • InjectionParameters which had IDetectorDependencies injected, and contained static references to all common services
    • IDetectorRegistryService and DetectorRegistryService which loaded additional classes from DLLs
      • This functionality designed to be useful before Component Detection was open sourced. Now that it is, detectors can be added more easily. This functionality was not used internally.
      • This means that IComponentGovernanceOwnedDetectors can also be deleted
  • Updating injected types
    • BcdeScanExecutionService and DetectorListingCommandService had IDetectorRegistryService injected. Instead, they now have IEnumerable<IComponentDetector> injected directly.
  • Init method for FileWritingService and Logger moved to their respective interfaces, IFileWritingService and ILogger
    • Orchestrator had the concrete types FileWritingService and Logger injected. However, it's best practice to inject interfaces instead. In making this change, some methods that were available in the concrete type had to be added to the interface
    • VerbosityMode needed to be moved from Common to Contracts to facilitate this
8. refactor: init TelemetryRelay during orchestrator setup

As TelemetryRelay is a static class, it cannot be instantiated using Dependency Injection. Instead, we inject an IServiceProvider into Orchestrator and call Init on TelemetryRelay with the required IEnumerable<ITelemetryService>

9. refactor: only one GraphTranslationService

The parameter IEnumerable<Lazy<IGraphTranslationService, GraphTranslationServiceMetadata>> was designed to allow loading of alternative graph translation services, dynamically, from DLLs. That functionality never materialized, and MEF is being removed, so I am simplifying that constructor parameter to be simply IGraphTranslationService.

If alternate graph translation services are required in the future, the constructor can be updated to take a IDictionary<IGraphTranslationService, GraphTranslationServiceMetadata>>. This can be added to the IServiceCollection as follows:

services.AddSingleton<DefaultGraphTranslationService>();
services.AddSingleton<FancyNewGraphTranslationService>();

services.AddSingleton<IDictionary<IGraphTranslationService, GraphTranslationServiceMetadata>>(sp =>
  new Dictionary<IGraphTranslationService, GraphTranslationServiceMetadata>>
  {
    { sp.GetRequiredService<DefaultGraphTranslationService>(), new GraphTranslationServiceMetadata { Priority = 1 } },
    { sp.GetRequiredService<FancyNewGraphTranslationService>(), new GraphTranslationServiceMetadata { Priority = 0 } },
  });
10. refactor: remove System.Composition usage

This includes:

  • Removal of [Export] and [Import] attributes
  • Conversion of public properties to private readonly fields
  • Removal of parameterless constructors
11. refactor: migrate YarnLockFileFactory and YarnLockFileParser to be managed by dependency injection
12. test: final test fixes
13. refactor: remove System.CompositionModel dependencies

🎉

14. refactor: fix misc analyzer warnings

@JamieMagee JamieMagee requested a review from a team as a code owner January 25, 2023 20:11
@JamieMagee JamieMagee force-pushed the users/jamagee/dependency-injection branch from 6af2dd6 to c87abe8 Compare January 27, 2023 16:52
@github-actions

This comment was marked as resolved.

@JamieMagee JamieMagee force-pushed the users/jamagee/dependency-injection branch from df76a9f to 0291e0f Compare January 30, 2023 05:00
@JamieMagee JamieMagee mentioned this pull request Jan 30, 2023
25 tasks
JamieMagee and others added 17 commits February 5, 2023 19:31
Currently, Managed Extensibility Framework (MEF) does property injection. In migrating to .NET's own Dependency Injection framework `Microsoft.Extensions.DependencyInjection` we need to migrate to constructor injection. This change adds 2 constructors for every class that has properties injected into it:

- A parameterless constructor (for MEF)
- A constructor with parameters for all the required dependencies (for .NET DI)

This is an intermediate step that allows MEF to work, while work continues on the migration to MEF.

See #400 for more information
This is the equivalent of `DetectorTestUtilityCreator` and `DetectorTestUtility`, but for .NET DI.

See #400
…lder`

This change migrates all of the component detector tests to us `DetectorTestUtilityBuilder`, based on .NET DI, instead of `DetectorTestUtility`, based on MEF.

This also adds a new base class for all component detector tests called `BaseDetectorTest` which constructs the `DetectorTestUtilityBuilder` in its constructor.
No longer required after all component detector tests have been migrated to use `DetectorTestUtilityBuilder` instead
This change uses the constructor that has parameters for all required properties of a service, instead of using object initializers (which actually use the parameterless constructor)
This change adds all of the services that will be managed by .NET's dependency injection framework to the `AddComponentDetection` method. This allows you easily configure all the services that Component Detection requires

```csharp
var serviceProvider = new ServiceCollection()
  .AddComponentDetection()
  .BuildServiceProvider();
var orchestrator = serviceProvider.GetRequiredService<Orchestrator>();
var result = await orchestrator.LoadAsync(args);
```
This is one of the final changes in migrating away from Managed Extensibility Framework (MEF) and to .NET's Dependency Injection (DI).

These changes include:
- Deleting classes that are no longer required for handling property injection:
  - `IDetectorDependencies` and `DetectorDependencies` which held references to all common services
  - `InjectionParameters` which had `IDetectorDependencies` injected, and contained static references to all common services
  - `IDetectorRegistryService` and `DetectorRegistryService` which loaded additional classes from DLLs
    - This functionality designed to be useful before Component Detection was open sourced. Now that it is, detectors can be added more easily. This functionality was not used internally.
    - This means that `IComponentGovernanceOwnedDetectors` can also be deleted
- Updating injected types
  - `BcdeScanExecutionService` and `DetectorListingCommandService` had `IDetectorRegistryService` injected. Instead, they now have `IEnumerable<IComponentDetector>` injected directly.
- `Init` method for `FileWritingService` and `Logger` moved to their respective interfaces, `IFileWritingService` and `ILogger`
  - `Orchestrator` had the concrete types `FileWritingService` and `Logger` injected. However, it's best practice to inject interfaces instead. In making this change, some methods that were available in the concrete type had to be added to the interface
  - `VerbosityMode` needed to be moved from `Common` to `Contracts` to facilitate this
As `TelemetryRelay` is a static class, it cannot be instantiated using Dependency Injection. Instead, we inject an `IServiceProvider` into `Orchestrator` and call `Init` on `TelemetryRelay` with the required `IEnumerable<ITelemetryService>`
The parameter `IEnumerable<Lazy<IGraphTranslationService, GraphTranslationServiceMetadata>>` was designed to allow loading of alternative graph translation services, dynamically, from DLLs. That functionality never materialized, and MEF is being removed, so I am simplifying that constructor parameter to be simply `IGraphTranslationService`.

If alternate graph translation services are required in the future, the constructor can be updated to take a `IDictionary<IGraphTranslationService, GraphTranslationServiceMetadata>>`. This can be added to the `IServiceCollection` as follows:

```csharp
services.AddSingleton<DefaultGraphTranslationService>();
services.AddSingleton<FancyNewGraphTranslationService>();

services.AddSingleton<IDictionary<IGraphTranslationService, GraphTranslationServiceMetadata>>(sp =>
  new Dictionary<IGraphTranslationService, GraphTranslationServiceMetadata>>
  {
    { sp.GetRequiredService<DefaultGraphTranslationService>(), new GraphTranslationServiceMetadata { Priority = 1 } },
    { sp.GetRequiredService<FancyNewGraphTranslationService>(), new GraphTranslationServiceMetadata { Priority = 0 } },
  });
```
This includes:
- Removal of `[Export]` and `[Import]` attributes
- Conversion of public properties to private readonly fields
- Removal of parameterless constructors
This can likely be done better or cleaner, but it's sufficient for now
@JamieMagee JamieMagee force-pushed the users/jamagee/dependency-injection branch from 0291e0f to 48dc640 Compare February 6, 2023 03:48
Copy link
Contributor

@cobya cobya left a comment

Choose a reason for hiding this comment

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

Reviewed offline and changes look good.

@JamieMagee JamieMagee added version:major breaking change Breaking change, requires major version bump labels Feb 14, 2023
@JamieMagee JamieMagee merged commit f6912c0 into main Feb 14, 2023
@JamieMagee JamieMagee deleted the users/jamagee/dependency-injection branch February 14, 2023 17:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
breaking change Breaking change, requires major version bump version:major
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Migrate away from Managed Extensibility Framework (MEF)
2 participants