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

Stop special-casing StorageAccountAttribute. #1056

Closed
wants to merge 1 commit into from
Closed

Conversation

MikeStall
Copy link
Contributor

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

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)
Copy link
Member

@mathewc mathewc Mar 13, 2017

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.

Copy link
Member

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"

Copy link
Contributor Author

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.

Copy link
Contributor

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)

Copy link
Contributor Author

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);
Copy link
Member

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?

Copy link
Contributor Author

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

Copy link
Member

@mathewc mathewc Mar 15, 2017

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.

Copy link
Contributor Author

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
Copy link
Member

@mathewc mathewc Mar 13, 2017

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.

Copy link
Contributor Author

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):

  1. No breaking changes
  2. no redundant attributes (having StorageAccount and Storage would be confusing and is unnecessary from a binding perspective)
  3. good names

I don't think we can get all 3. The current PR does 1&2.

Copy link
Member

@mathewc mathewc Mar 13, 2017

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.

Copy link
Contributor Author

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?

Copy link
Member

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.

Copy link
Contributor Author

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)
Copy link
Member

@mathewc mathewc Mar 15, 2017

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)
Copy link
Contributor

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)))
Copy link
Contributor

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)

@ahmelsayed
Copy link
Contributor

doesn't ServiceBusAccountAttribute.cs have similar special handling?

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.

5 participants