-
Notifications
You must be signed in to change notification settings - Fork 838
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
Make the config object model read-only #662
Conversation
@@ -16,11 +16,11 @@ public interface ITransformBuilder | |||
/// Validates that each transform is known and has the expected parameters. All transforms are validated and | |||
/// so all errors can be reported. | |||
/// </summary> | |||
IList<Exception> Validate(IList<IDictionary<string, string>> transforms); | |||
IList<Exception> Validate(IReadOnlyList<IReadOnlyDictionary<string, string>> transforms); |
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 looks crazy 😄
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.
Yeah. We'll see if I can sort it out as part of #60 (coming soon).
I like it! |
samples/ReverseProxy.Config.Sample/ReverseProxy.Config.Sample.csproj
Outdated
Show resolved
Hide resolved
src/ReverseProxy/Abstractions/RouteDiscovery/Contract/ProxyRoute.cs
Outdated
Show resolved
Hide resolved
src/ReverseProxy/Service/Config/ProxyRouteTransformExtensions.cs
Outdated
Show resolved
Hide resolved
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.
Not a full pass through the change. I'll continue on Monday.
src/ReverseProxy/Abstractions/RouteDiscovery/Contract/ProxyRoute.cs
Outdated
Show resolved
Hide resolved
/// </summary> | ||
public static ProxyRoute WithTransformPathSet(this ProxyRoute proxyRoute, PathString path) | ||
{ | ||
return proxyRoute.WithTransform(new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase) |
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.
When initializing from the config we went an extra mile and every Dictionary/List properly wrapped in their ReadOnly counterpart. But here we're forgoing that and just passing Dictionary itself.
So either we do care that everything is properly readonly and nobody cannot even cast the instance to non-readonly counterpart. Or we can quite substantially simplify the config reading.
Or is there any other argument why is it here more lax?
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.
There is some awkwardness around IDictionary vs IReadOnlyDictionary vs Dictionary. Dictionary implements both interfaces, but IDictionary does not implement IReadOnlyDictionary and requires wrapping. Many of the config and linq APIs only return an IDictionary.
Dictionary is easy to use here and we don't have to worry about it being modified because nobody else ever has access to it without downcasting or other tricks. I can try a ReadOnlyDictionary to see if that's as easy.
Will this change have any effect on the runtime model ( |
The main goal is to get rid of the DeepClone code. I don't expect many changes to the runtime model, but we may be able to remove some redundancy. |
944f3a9
to
e38a95e
Compare
src/ReverseProxy/Abstractions/ClusterDiscovery/Contract/ActiveHealthCheckOptions.cs
Outdated
Show resolved
Hide resolved
There was actually a lot of redundancy on ClusterConfig I was able to remove. There were lots of Cluster* types that were read-only copies original config that we didn't need anymore. |
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.
Look cleaner with the records!
@@ -329,5 +338,13 @@ private static bool ContainsKey(string expectedKeyName, ReadOnlySpan<char> actua | |||
keyRemainder = actualKey.Slice(expectedKeyName.Length); | |||
return true; | |||
} | |||
|
|||
private class RouteHeaderFields |
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'm not a fan of this extra class but I guess there's no reasonable way around it unfortunately.
@@ -23,49 +21,22 @@ public sealed class ClusterConfig | |||
{ | |||
public ClusterConfig( |
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 we even get rid of ClusterConfig
? Maybe not necessarily as part of this PR, but in general.
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 we still need it. It serves as an atomic unit of state that's updated on the ClusterInfo and snapshotted with requests.
c3d9622
to
243e82a
Compare
Hello @Tratcher! Because this pull request has the p.s. you can customize the way I help with merging this pull request, such as holding this pull request until a specific person approves. Simply @mention me (
|
Fixes #179
Today Cluster, ProxyRoute, etc. are mutable objects used to build the initial data model. Because these are mutable we clone them several times to prevent anyone from modifying them when we don't expect it, such as before calling user callbacks.
To avoid extra copies we want to make the input object model immutable but still have it be easy to construct. Here I've experimented with using C# 9 record types to achieve that. A record type is an immutable class with automatically generated methods like Clone and Equals. Using
init
only properties makes the objects still easy to construct. The newwith
operator allows making mutated clones of records so we can still manipulate the objects when we need to but not worry about someone else changing them unexpectedly.There's a lot of churn-in-place here, but I'm happy with the results. All DeepClones were removed and I was able to remove many redundant classes from RuntimeModel that were only there as read-only copies of the config model.