Replies: 1 comment 1 reply
-
I have chatted with @IvanGoncharov on this a bit over the last couple of weeks. I am also leaning towards making directives better. Hot Chocolate and GraphQL-Java, for instance, have implemented the However, I am not entirely happy with it as it is today. The idea that Ivan had with the If we could have something like In the current proposal, we are serializing the arguments as a GraphQL literal string, which is not nice since we now have to parse twice the introspection result. A proper representation would be especially nice in cases where you have arguments that are input objects. I also rethought the issues with the pipelining of directives and think it actually is a good thing, and we should aim to expose them as a list. Meaning that type system members should have a list of applied directives, and the type could, in the case we go with the In any case, I look forward to the meeting and the discussions. |
Beta Was this translation helpful? Give feedback.
-
This discussion is a spin-off from other new discussion Extending introspection with custom metadata. Two competing approaches for custom meta data recently emerged as possible solutions - meta-types and directives (they both are in fact old ones). I think mixing in one thread multiple approaches would make it difficult to follow, so it is better to have a separate discussion for each.
So I start this one for a specific solution - exposing ALL directives through introspection by adding an appliedDirectives field to standard introspection types. This leaves the other discussion to explore the meta-types path.
This discussion is in fact a restart of an old, long-standing issue #300 Expose user-defined meta-information via introspection API in form of directives. It is a 5-year long thread, with many posts, and I think it's better to do a fresh start here, in WG discussions.
Here is a short Summary of this long post if you want to go straight to conclusions.
Suggested solution
Simply add appliedDirective field to all introspection types for elements that can be decorated with attributes. What follows below are some observations, arguments in favor and responses to counter-arguments against this approach.
Main argument for appliedDirectives path: similarity of directives to already known facilities in other platforms
For most developers the directives in GraphQL seem like immediately familiar and known concept, something they already have in .NET (attributes) or Java (annotations). Also, GraphQL provides introspection, and .NET and Java have similar facilities. Seasoned developers know the power of this combination (custom meta data plus introspection) and are initially happy that it is available in GraphQL - I was!
However, they are faced with a disappointment - directives are not available through introspection. Here is a guote from original Issue 300:
I think this reflects the feeling of many, if not the majority of developers coming to GraphQL. Whether this similarity of directives to Attributes/Annotations was intended or not in original design , it does not matter now - it is already there, and it is a strong factor for design decisions by the Working Group (WG).
But it's not just the similarity. The important fact is that these similar facilities in other platforms were VERY SUCCESSFUL over 20+ years. Developers know that the runtimes (CLR, JVM) use them internally. Many successful frameworks rely one these facilities heavily for things like AOP, transaction control, runtime type behavior, additional tips for UI, etc. The developers themselves use them regularly in their projects. As a result, if we go with any solution other than directives, it will be very hard to explain to the public why - everybody else was successful on this path.
Considering that directives are not going away and stay, just for backwards compatibility, if we go with any other solution for meta-data, we will end up with TWO metadata-like systems, which again would be hard to explain.
Is it really custom meta-data we are talking about?
I think the term itself 'custom metadata' is misleading. It suggests, semantically, that while there is a meta-data set of types ( __Field, __Type), our goal is to provide a way to extend these types, because what we want to add is also meta-data, just custom. I believe this is quite misleading.
Let's look at .NET as an example. There is a meta-class FieldInfo which describes a field. It has a property Attributes which is a list of attributes applied to the field in source language (c#). If a programmer wants to supply some extra info about a field, he does NOT extend FieldInfo class, he simply slaps an attribute on the field, with the info inside it. The code interested in this info can find it in FieldInfo.Attributes list. The information itself does not have to be of any specific 'kind', nothing of 'meta' nature, whatever stuff you want. It is not extension of meta-type FieldInfo, it is just a small piece of data that programmer wants to send to the execution engine or any component interested in this info.
What we have here is the following. .NET provides a slot in the meta-class to put some extra data for the programmer. The data itself is no concern to CLR (.NET engine) - put whatever you want. This is a small communication channel between parts of the application - from declaration to some components interested in this data. The semantics of the data is a mini-contract defined within the scope of the application, or between a framework/library and the code using it.
For example, a JsonSerializer library defines a *JsonProperty(name)" attribute; a programmer using the library can put this attribute on a field if he wants a specific name to be used in Json for the field. This is a contract between a library and it's users. The important point here is that since it is a limited scope/audience contract, all the tools or the rest of the ecosystem do not need to know any semantics. And it is simply impossible. It's enough that introspection tools can just show the custom data on introspection types, since it has simple predefined structure - a named set of key-value pairs. It is easily observable/displayable by any introspective tool.
For GraphQL directives it would work the same way. We can view a custom app as a combination of client and service components. If custom GraphQL service defines/implements a few custom directives and shares their semantics with the client developers, then it is a custom micro-contract between two parts of an application. It applies to cases when the client and service teams belong to the same company, or when service team belongs to a company that publishes GraphQL API for everybody's use, and people use it (ex Github). In this case the company (Github) can define some custom directives for clients to use. This is a custom micro-contract on top of GraphQL, for a limited group of API users.
It would be enough if Graphiql UI tool just displays custom directive names with arg values; it does NOT need to understand them or act on them - they are part of a custom contract. This note is in regards to discussion on how much the directives must be 'standardized' so that custom tools can 'understand' them in some way.
Conclusion: The data we are discussing here is not a custom meta-data. It is a small-size custom data attached to meta-elements in slots provided in meta-types by the platform. There is no assumption about the nature of this custom micro-data. The goal is to enable custom mini-contracts between components in the application.
Arguments against the appliedDirectives approach
There had been several arguments against appliedDirectives put forward over the years, and re-stated recently with a push to meta-types. See Benji's presentation at GraphQL conference. I would like to discuss them here.
1. Secrets in directives in existing schemas
This had been long-standing objection. People say that they already have schemas which contain some confidential information in custom directives, so exposing these directives will be a security issue. WG members actually acknowledged this being an issue in the past. However, we need to change our stance here. This practice is actually severe violation of common, basic security principles. Here is why.
We should not encourage this practice by acknowledging this 'secrets in dirs' as a valid concern. Instead, we should clearly state: Stop doing this, it is very dangerous, it is a violation of basic security rules.
2. Directive word is a call/order to act
The argument goes like this: "Directive means order to do something, so creating directives with random name/purpose does not align with this". Counter-argument is simple - standard directives deprecated and specifiedByUrl do not pass this test, they are FYI for the programmer, not a call for action by the engine. In general, the keywords/names chosen for language elements are historical accidents, they do not need to precisely match the semantics of the keyword in the language, it's impossible, just close enough is good enough. .NET uses Attributes, Java - Annotations, JS and TypeScript - decorators. Following this logic, decorators in JS are used to make code prettier.
3. Directives are limited
It means directives can use only scalar or input types as arguments. But .NET attributes are limited too, arguments can be only simple types 'evaluatable' at compile time. That does not seem to be a big problem for .NET developers.
4. Returning input types in introspection query is not allowed.
It means directive's argument might be of an input type, but GraphQL spec forbids using input types for output. This is a long standing request from developers to allow returning input types. There is no technical reason for this restriction; input types should be seen as restricted Object types.
The other problem is returning any-type value in some field. The arguments of directives may be of any scalar or input type; in introspection we need to return them, and there is no Any type for 'value' field in a hypothetical __Arg introspection type. Currently, we have the same problem with returning a default value for arguments, and the Spec uses a workaround - return string representation of the value. This is OK for occasional default values, but not feasable for all arguments of directives; we better provide a real solution instead of a workaround.
We need a type Any to return any value, and also Map to return complex type as a set of key-value pairs. This is something that I think is needed not only for this case, but there is a need for these 'types' in general, there were requests to add them. In fact, some engines actually implemented it as far as I know.
As some members in WG say for new feature requests - "Show me the real case". This is the real case, inside GraphQL's own introspection system. The new Struct initiative among other things suggests fixing these issues, hopefully all of them, including Any, Map. On the other hand, it is hard to see how alternative solutions can avoid the same challenges.
5. Counter-Argument: Possibility of mis-use - too many directives in one place
@benji brings up this scenario in his presentation. Developer decides to use a custom directive to provide a custom label for a field. Later he needs to expand it for multiple languages, so he might end up with large number of directives on every field. That's not a valid argument. No sane developer would do that. If he does - he ends up with a very bloated schema, SDL file becomes hard to read, as well as introspection queries become slow. We can't prevent this, nor should we even try. Imagine java language designers raised a concern - what if somebody writes a method with 10K source lines, how to terrible it would be?! Methods should be max 100 lines, that's a good coding practice! Did they put a limit 100 on method length? No. If somebody wants to do an unreasonable thing, you can't stop him.
In practice, in .NET you rarely find a type or a field with more than 5 attributes. Developers are reasonable people.
Summary
Given these factors, I actually do not see any other way/solution we can go, other than this 'appliedDirectives' path.
References
See also my counter-arguments after this presentation
Remarks
Dear reader, if you agree with these arguments and would prefer this approach, please do not be silent, please post a short comment or simply add a Like . Or a Dislike and a comment if you disagree. We need to know the opinion of the broad GraphQL community regarding this important issue and the suggested approach.
Beta Was this translation helpful? Give feedback.
All reactions