-
Notifications
You must be signed in to change notification settings - Fork 434
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/More proper use of DI for Synchronizer #7500
Conversation
Co-authored-by: Lukasz Rozmej <lukasz.rozmej@gmail.com>
Co-authored-by: Lukasz Rozmej <lukasz.rozmej@gmail.com>
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.
Highly appreciate the detailed summary.
I'm sure many of you have experience that changing a class means changing 6 other files that just wire the main class that you are changing.
Agree, this is rather annoying but it's not a dealbreaker in my opinion.
Lazy initialization and automatic dispose.
I think this is a killer feature, in particular lazy initialization that removes the need to keep track which things are initialized first.
Pluggability
I also think this is a really important feature considering how we're moving towars a plugin-based architecture. We had to fall back to static properties during the Transaction refactoring (#7313) due to the lack of hooks or required initialization order.
The configuration for dependency injection can be complicated maybe it will make the codebase more complicated.
I don't think so. Initialization code is already complicated and has a lot of ad-hoc mechanisms like "WaitForX" to make it work. A unified DI container based approach should be more manageable.
Even now, it can remove some unnecessary boilerplate
This is good. We're closing to ~300.000 LOC and any deletion is welcomed.
Help me out here, let this PR land first, then that can be clean up and more importantly verify and test if it still works.
I'll approve since I agree with the vision and the proposed PR contains reasonable, localized changes (as you mention, let's go slowly step by step).
The end goal is in fact single container. The problem is that, we don't have complete components.
- For example, let say we have service, a, b, c, constructed in step A, B, C. b depends on a, and c depends on b.
- Let say b is not migrated, but is used by c.
- If we use single container, during the container building (which is gonna happen in A), b is not gonna get constructed yet at - that point, so it is not in the container and so c initialization will fail.
I don't fully understand the example. Do we have a
and c
migrated? If so, I think the problem is that we migrated in the wrong order, that is, start from the leaves and move upwards (c, then b, then a). Maybe you could elaborate further?
Regarding the proposed solutions to this problem, I would avoid the introduction of lifetimes or Autofac magic for these intermediate refactoring steps.
Well, yes. But the point is to allow migrating out of order. |
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.
Could you provide like a big picture of into what specific modules and components you would split the system
@@ -6,6 +6,8 @@ | |||
<PackageVersion Include="AspNetCore.HealthChecks.UI" Version="8.0.1" /> | |||
<PackageVersion Include="AspNetCore.HealthChecks.UI.Client" Version="8.0.1" /> | |||
<PackageVersion Include="AspNetCore.HealthChecks.UI.InMemory.Storage" Version="8.0.1" /> | |||
<PackageVersion Include="Autofac" Version="8.1.0" /> |
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.
Why is Autofac needed on top of Microsoft.Extensions.DependencyInjection? If because of modules - can it be replicated by using separate IServiceBuilder
s instead?
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 would be my question as well. I believe Autofac
provides named registrations while Microsoft.Extensions.DependencyInjection
may lack it. Not sure if this is the case.
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 was wrong. ServiceKey
etc are the way to go.
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.
For named registrations, there's IKeyedServiceProvider
.
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 think that in its current form, this brings not that much. More deletions, less configuration in tests, one container. That would be my goal. |
Co-authored-by: Alex <alexb5dh@gmail.com>
Autofac because it has more feature, which can be a downside that is true. Specifically the main one is the ability to create and reconfigure hierarcy of lifetime. In this example, it is used to support full sync and fast sync, which use the exact same type set of service but only change Also, with a simple registration source (not in this PR), you can inject I tried single container before, the cost is severe. See #6483, #6531. At least with multiple container at first, we would already have the module configuration. More reduction in boilerplate can be seen here https://github.com/NethermindEth/nethermind/pull/7520/files#diff-2d1d749000abfdbd86d6f5187b2765d94b7e83dcb99dcb55f9f4d51fecf08c16. That will be after this PR. |
In the last attempt there is a:
Plugins are literally more module. I guees whats left is block producer and rpc. And network was not complete at that time.. |
Introduce DI
Why?
Reproducable service construction.
Easier code change.
Lazy initialization and automatic dispose.
Pluggability
TrieStore
orPaprika
orPortal
orDiscV4/5
into an Autofac Module. The next question is then which part do you want to make plugin (and to be clear, you dont have to), not which hook to add.Better developer experience.
Q&A
Do all code need to support dependency injection.
CompositeDiscoveryApp
, and that big class can be resolved with DI.The configuration for dependency injection can be complicated maybe it will make the codebase more complicated.
This PR only modify some of part of the codebase while kinda hackily inject the rest of the dependency into the container. What about the construction of the dependencies?
Module
along the way.IContainer
and this IS an antipattern.IBlockDownloaderFactory.cs
.A lot of code is not compatible with DI because of mismatched constructor or mutating config/dependency at runtime or nullable dependency. We should modify them first.
INethermindApi
, cyclic dependency, and lifetime.This constructor clearly can use
ISyncFeed[]
and we can loop them all and wait for them. And we can just don't declare some of the feed instead of passing in a NoopSyncFeed.This
<insert class name here>
class can also be dependency injected.InitializeNetwork
can be dependency injected, then theMergePlugin
can be injected and so on.InitializeNetwork
specifically you can look at this branch which migrate the wholeInitializeNetwork
.Test code shows that now Synchronizer is completely opaque: there is no way to know that we have to build a IServiceCollection with those specific services without looking into the implementation.
.Give me a vision.
INethermindApi
.IStep
is removed as it only construct but not run.IStepMeta
is used to declare steps, its dependency and dependents.IStep
for theIStepMeta
is only resolved on its turn, meaning that if the step does not need to be constructed if it does not need to run.FullAppStep
that starts other needed stops. Maybe you just needVerifyTrieStop
, orEraImportStep
.Module
, registered after all core module, lets call itNethermindModule
, which essentially have all components needed to start nethermind.TestEnvironmentModule
which replaces db and network.NethermindModule
, beforeTestEnvironmentModule
.Changes
ISynchronizer
completely from container.INethermind*Api
would fetch from container if implemented. SinceISynchronizer
also forwardISyncModeSelector
andISyncProgressResolver
, might as well directly get them from container also.MergeSynchronizer.ConfigureMergeComponent
.ILogger
, andIConfig
, andIDb
and friends, but not implemented here.Types of changes
What types of changes does your code introduce?
Testing
Requires testing
If yes, did you write tests?
Notes on testing