-
Notifications
You must be signed in to change notification settings - Fork 356
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
Stop special-casing StorageAccountAttribute. #1056
Conversation
Make the storage attributes (Blob,Table,Queue) derive from StorageAccountAttribute. This gives Blob* attribute a Connection property, just like all the other attributes. This is not a breaking change - and no change to public tests. The key is in TypeUtility's new GetAttr function. It still walks the hierarchy and collapses attributes into a single attr. The motivation here is: 1. Stop special-casing blob storage. All other resources need something similar here. 2. Provides a 1:1 between Attributes and Function.json that lets us removes corner cases in Script and tooling
// higher up in the hiearchy | ||
ICustomAttributeProvider provider = parameter; | ||
|
||
while (provider != null) |
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 already an existing GetHierarchicalAttributeOrNull that was doing this walk up from parameter/method/class. Why the new code for this? Recommend we just use what we already have - it is fully tested, etc.
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.
Also the naming makes it clear from the caller that a hierarchical walk for the attribute will be performed. This isn't clear from "GetAttr"
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.
GetHierarchicalAttributeOrNull doesn't handle base classes. We do leverage existing tests on the new code. I can probably remove GetHierarchicalAttributeOrNull so that there's only one copy of this.
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.
Just to help clarify - this will hierarchically merge the properties of all found attributes which are of type TAttribute or a base class of TAttribute?
As a more concrete example, GetAttr merges StorageAccountAttribute as well as TableAttribute (because TableAttribute : StorageAccountAttribute
)
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.
Correct
if (currentValue == null) | ||
{ | ||
var value = prop.GetValue(attr); | ||
prop.SetValue(attributeSource, value); |
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 this getter function setting things?
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.
We have to merge the properties together into a single attr. Say we have:
[Blob] [Storage(account="X"]
We need to set Blob.account = x
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 need to come up with a better way to do this. This attribute merging is hacky, and isn't a pattern we want to set in stone like this. We're not going to promote a pattern of attributes deriving from another attribute, with base or derived instances found up and down the hierarchy.
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.
That's the heart of the proposal... We started down this path as soon as we split out a single attribute into multiple attributes. We need some means to associated the different attributes together.
@@ -26,7 +26,7 @@ namespace Microsoft.Azure.WebJobs | |||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1813:AvoidUnsealedAttributes")] | |||
[AttributeUsage(AttributeTargets.Parameter)] | |||
[DebuggerDisplay("{QueueName,nq}")] | |||
public class QueueAttribute : Attribute | |||
public class QueueAttribute : StorageAccountAttribute |
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.
The "is a" relationship here kind of doesn't make sense. If we had a StorageAttribute (rather than StorageAccountAttribute) it does make more sense. QueueAttribute "is a" StorageAttribute. What if we just introduced this new StorageAccount base type and used that? That's a cleaner type model I think.
We'd then leave StorageAccountAttribute as is for back compat (as well as method/class level specification), and adjust the GetStorageAccountAsync logic accordingly.
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.
Ideally, we want (and I think these are in priority order):
- No breaking changes
- no redundant attributes (having StorageAccount and Storage would be confusing and is unnecessary from a binding perspective)
- good names
I don't think we can get all 3. The current PR does 1&2.
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.
Adding the new StorageAttribute base class would be non breaking. All the types make sense together, and in the next major version, we can simply remove the Parameter level usage from StorageAccountAttribute - it then stays around for class/method level overrides.
I'm thinking of the future when we might have more "storage" common properties. Those would make sense to add to a StorageAttribute base class, but not a StorageAccountAttribute class - the naming there is specifically for holding a single bit of info - the account name. StorageAttribute base class sets us up nicely for future extensibility.
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 it violates #2 ... a separate Storage and StorageAccount attribute seems confusing. What's the difference between them? What's the customer value in having 2 separate attributes?
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.
The value is to us as framework authors. We have a base attribute class that is extensible and makes more sense from a type perspective. StorageAccountAttribute is for specifying a storage account name value - that's why I named it the way I did. It was not designed as a base class - only to specify a single account name. StorageAttribute (abstract base class) is the base class for all storage binding attributes.
This won't be confusing for our users - the base class is abstract and they won't even think about it.
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.
Does StorageAccount : Storage; Blob : Storage? What's a specific difference between Storage and StorageAccount?
var attrs = provider.GetCustomAttributes(typeof(Attribute), false); | ||
foreach (var attr in attrs) | ||
{ | ||
if (attributeSource == null) |
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.
It's not clear to me at all what this code is doing. Some good code comments in this method would go a long way. What it looks like to me is that if we're processing say GetAttr, if it wasn't found on the ParameterInfo initially above, you're looking for it on the Method/Class as you walk up. That doesn't make sense - our binding attributes are only parameter level attributes.
// higher up in the hiearchy | ||
ICustomAttributeProvider provider = parameter; | ||
|
||
while (provider != null) |
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.
Just to help clarify - this will hierarchically merge the properties of all found attributes which are of type TAttribute or a base class of TAttribute?
As a more concrete example, GetAttr merges StorageAccountAttribute as well as TableAttribute (because TableAttribute : StorageAccountAttribute
)
continue; | ||
} | ||
|
||
if (attr.GetType().IsAssignableFrom(typeof(TAttribute))) |
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 this could benefit from more comments or splitting this function up into multiple pieces to help with readability (basically, separate the walk step from the merge step)
doesn't |
Make the storage attributes (Blob,Table,Queue) derive from StorageAccountAttribute. This gives Blob* attribute a Connection property, just like all the other attributes.
This is not a breaking change - and no change to public tests.
The key is in TypeUtility's new GetAttr function. It still walks the hierarchy and collapses attributes into a single attr.
The motivation here is: