-
Notifications
You must be signed in to change notification settings - Fork 94
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
.graphqlrc
redesign proposal
#16
Comments
Great write-up, @asiandrummer. I think the main thing that I'd want to see clearly defined is how to resolve paths:
A question: might we want to allow arrays here, with a first-round-wins resolution algorithm. I'd also like to make sure the discovery algorithm for the files themselves is very clearly spelt out (like you allude to above). We already have a working example internally, so that would be a good starting point to kick off the discussion. |
Thanks a lot @wincent for kicking this off! Additionally to your proposed design I'd like to bring up the following thoughts:
Regarding the resolution of an URL endpoint: I also want to loop in @stubailo as he's maintaining eslint-plugin-graphql. |
I could honestly go either way on this project continuing to support graphql-js. I think the most maintainable method long-term is to go through the public http graphql api. The main advantage for our architecture was that accessing the schema.js file directly avoids having to deal with the extra cruft that comes with authenticating / permissioning a request from the babel plugin. We could rearchitect our app to special case a request from the babel-plugin (either checking for localhost or maybe set a 'X-Babel-Relay-Plugin' header with some kind of verifiable secret?) But this also seems fraught and viral. This brings us back to generating a schema.json file on every save, either manually or hooked into our watcher, which brings us back to where we were in the first place a year ago. Would love any guidance from you guys on best practices here. |
I am for the having the ability to pass my endpoints into an ENV and populate via webpack, this provides flexibility when making a simple boilerplates or prototypes. Also provides flexibility when deploying, being able to switch endpoints based on a NODE_ENV would be nice. Also having the ability to point to production and dev endpoints in a .graphqlrc would be a nice addition, though I haven't had a use case as I have mainly only used graphql for side projects. I could see myself need to point to set two separate endpoints in a single app. |
Thanks for your opinions! I'll try addressing each comment below: cc @wincent:
Your explanation makes sense. I agree that resolving It is worth mentioning, though, that GraphQL recommends the authorization to reside under the business logic layer - if possible I'd encourage the authentication before introspection (cc @akre54).
I'll mention this later, but to support the case that cc @schickling
Although I think this is a good example for each app to customize their settings, I'm not sure if we should recommend the custom HTTP headers in a
I'm not super experienced with working with env variables, but read through the 12 factor apps and the idea sounds cool. So would the GraphQL configurations be stored within whenever we store them, such as One concern I have is that we don't have one unified place to store them - for example Java/Python GraphQL services have different ways/conventions to use env variables. Maybe we could store the I like including a way to generate GraphQL schema from js file(s) - it's pretty cool how you've done it in the PR 👍 cc @bdougie
I like the idea:
I like to have one source of truth for my environment, but I could see how one might use this to generate a boilerplate schema and test mutations, for example. |
If we have something like prod/dev, we should definitely not limit it to exactly two options. Alternatively, people can just have multiple |
@stubailo currently the suggested mechanism to choose which I'd like to see what people think about prod/dev. I guess the workaround for prod/dev is to have different app configurations:
Maybe that's more desired. |
1. Env instead of sub appsI think it is not good to allow So if we have one schema in app, we may use standard config: {
"schema-path": "url/to/your/schema",
"input-dirs": [
"foo/bar",
"foo/bar/baz"
],
"exclude-dirs": [
"a/b/c"
],
"custom-validation-rules": "./customRules.js"
} If we need multiple schema setups, we may add prod/dev/... environments, via adding {
"schema-path": "url/to/your/schema",
"endpoint-url": "http://localhost:3000/graphql",
"exclude-dirs": [
"a/b/c"
],
"env": {
"production": {
"endpoint-url": "http://example.com/graphql",
},
// if we want to add additional schemas, it may be done also as environment
"app2dev": {
"schema-path": "url/to/your/schema-app-2",
},
"app2prod": {
"schema-path": "url/to/your/schema-app-2",
}
}
} And we will have ability to introduce
Anyway sub app division is a bad idea (in one 2. Camel-case instead of kebab-caseAlso it will be cool if config options' names migrated from kebab-case to camel-case. To have ability seamlessly add |
Thanks a lot for your great input @nodkz. I completely agree and am a big fan of @asiandrummer what are your thoughts on multiple |
@schickling I'd like to think that each If we're to support env variables, I think we should make sure they also contain For this reason I think the previous suggestion + some additions make a good format candidate: export type GraphQLConfigurations = {
[appName: string]: GraphQLAppConfig
} | GraphQLAppConfig;
export type GraphQLAppConfig = {
// Treat below config as a "default" set for each app
schemaPath: URL | FilePath | {
endPoint?: URL,
// If `.js` is supplied below, will try to use `graphql-js` to generate schema
localSchemaPath?: FilePath
},
inputDirs?: Array<DirPath>,
excludeDirs?: Array<DirPath>,
customValidationRules?: FilePath | Array<FilePath>,
// If env is specified, use one of the app configs in here
env: EnvironmentVariable
};
export type EnvironmentVariable = {
production: GraphQLAppConfig,
development: GraphQLAppConfig,
// but really just the below
[envVarName: string]: GraphQLAppConfig
}; Keep in mind that for the most use cases we'd only have one schema + one config - then we'd just have a very small object: {
"schemaPath": "uri/to/endpoint/or/path",
"env": { "production": "prodUri", "dev": "devUri" }
} The above would be kind of a protocol/specification of what we'd expect if use cases become more complex over time. I love these suggestions - let's keep this up, agree on a draft spec, and then get moving fast! |
@asiandrummer can we split Also
So in JS config under some environment can be fastly assembled in such way without deep merging: opts = { ...config, ...config[GRAPHQL_ENV || NODE_ENV || 'dev']}; Dot config files intended mostly for programs/tools and less for humans. |
Also, I have addition about clarifying what exactly is provided under
|
@asiandrummer not to get too off-topic, but that link only refers to permissioning specific resources, not accessing the GraphQL endpoint. Our API is behind a Passport user login scheme, and all requests have to be authenticated and tied to a user. This makes it difficult to work with the babel plugin (at least the version that came out 7 months ago). |
Yeah, that's what I meant. (I meant to write "first-found-wins", too, BTW.) My thinking here is that you might want to try an introspection URL first, and if that fails for whatever reason (for example, you are still in the process of setting up the endpoint), you can fall back to a default schema that you've stashed on the filesystem.
|
I think it's important to preserve some flexibility here with respect to how people structure there apps. Here are some use cases that I'd like to support: Single app, single schemaThis is a simple repo containing the files for exactly one app and one schema. It will be most natural for the In this scenario, it will be useful to have all the config at the top-level so that people can get started easily. But it's also going to be handy to permit switching based on Multiple apps, in well-isolated directoriesHere we have more than one app in a single repo, and we might want a structure like this:
Here we have application-specific config in Also note another idea here: that we can set common defaults in Here again, we could optionally make use of an "env" object, directed by Multiple apps, overlapping filesHere we have more than one app in a single repo, but the files are jumbled together in some way:
Because |
@wincent i think your last case is some exotic, looks more like a poorly structured code. I think that .graphqlrc should not support such case. Do .flowconfig, .babelrc, .eslintrc and other tools support such case? BTW As solution for such case, i can suggest to use symlinks to |
Maybe, but I fully expect that kind of thing to exist in the real world. Not sure what |
I would also think this is possible if an organization with pre-existing directory structure like that wants to use GraphQL and set up GraphQL configuration.
@nodkz actually your example is a correct application of my loosely-flowtyped config format ;) Sorry about the confusion. |
Okay, so in trying to create more momentum, let me propose a draft "protocol" for GraphQL configuration. DISCLAIMER: THIS COMMENT WILL BE LONG - for tl;dr; look at the first part of each paragraph. But first, a little note about using a "protocol" word - I'd like to think of GraphQL configuration standard as "suggestions", not "capabilities" or "requirements. I understand by saying "protocol" or "specification" it sounds like something we'd have to adhere to, but as we all understand, it's incredibly difficult to catch all the configuration use cases possible and have one single document that lists out requirements for those use cases. I think our goal should be to create a minimal set of shared GraphQL configuration standard that all GraphQL applications/tools can understand. As we come across different use cases that add high value to this, we can consider them and update the standard upon agreement. A more informal way to think of this would be starting out with configuring to get GraphQL schema (a minimal config), and add Also, for the ease of understanding what each configuration option means, I'll go through the most common use cases @wincent provided above. (If you have a better suggestion for calling this other than "protocol", "standard", or "specification", please let us know!) GraphQL Configuration protocolIn a nutshell, the simplest format is: export type GraphQLConfiguration = {
schemaPath?: FilePath, // may be .json or .graphql
schemaUrl?: URL,
}; As we come across more complex use cases, this may evolve into: export type GraphQLConfigurations = {
[appName: string]: GraphQLAppConfig
} | GraphQLAppConfig;
export type GraphQLAppConfig = {
// Treat below config as a "default" set for each app
// If below path extname is .js, try parsing JS schema definitions.
schemaPath?: FilePath,
schemaUrl?: URL,
// If env is specified, use one of the app configs in here
env?: EnvironmentVariable
// For multiple applications with overlapping files, bottom configuration options may be helpful
inputDirs?: Array<DirPath>,
excludeDirs?: Array<DirPath>,
// If you have customized validation rules to run
customValidationRules?: FilePath | Array<FilePath>,
// If you'd like to specify any other configurations, we provide a reserved namespace for it
optional?: Any
};
export type EnvironmentVariable = {
production: GraphQLAppConfig,
development: GraphQLAppConfig,
// but really just the below
[envVarName: string]: GraphQLAppConfig
}; Note that I'd like to discourage the recursive pattern in using the
Now the reasoning for it: let's start with a most simple use case - one app/one schema. Single-app/single-schemaUsually a single app requires a schema path/url and an env variable to facilitate the build and deployment: export type GraphQLAppConfig = {
schemaPath: FilePath,
schemaUrl: URL,
env: EnvironmentVariable
};
export type EnvironmentVariable = {
[envVarName: string]: GraphQLAppConfig
}; Multiple-apps/isolated directoriesFor multiple apps with isolated directories, I like @wincent's idea about a common-default GraphQL configurations, but in spirit of keeping things simple at first, I'd like to propose an one-config-per-directory approach as our first draft. I think there's an edge case where we'd have multiple parent GraphQL configurations to consider:
This can become a discussion of its own, which we can have separately ;) Multiple apps/shared filesFor multiple apps with shared files, we can have one GraphQL config on the top directory like @wincent suggested. This would create a GraphQL configuration such as:
As @nodkz suggested, if you'd like to solve this use case using symlink + the one-config-per-directory, that should be completely possible. Personally, as symlinks won't solve a more complex case of having commonly shared code within each application code (it's gnarly, but something completely possible to happen in a huge codebase), I feel more strongly about a GraphQL configuration with multiple application settings. Each approach has pros/cons I believe: for example, for tools that require generating GraphQL schema on the fly (such as a language server/service), treating one GraphQL config as a source of truth is useful, whereas for webpack config + building and deploying separate GraphQL config per apps/directories makes more sense. Custom validation rulesYou may want to run custom validation rules before building GraphQL apps/codegen/etc - Reserved namespaces@wincent made a suggestion to provide a way to include any other configurations if you desire. This namespace will contain an app/prod/organization/team-specific GraphQL configurations, and it will be treated as optional settings you'd like to use for your purpose. I've proposed this namespace to be called What is this repo, then?I think this repo should serve two roles in a broader sense: 1. Maintain this protocol and become a town hall for discussions/improvements for this protocol. An example of the reference implementation of the helper method: we mentioned a way to find this GraphQL configuration is to walk up a directory until it finds one. This basically can be implemented as such: /**
* (From `graphql-language-service` repo:
* Finds a .graphqlrc configuration file, and returns null if not found.
* If the file isn't present in the provided directory path, walk up the
* directory tree until the file is found or it reaches the root directory.
*/
export function findGraphQLConfigDir(dirPath: string): ?string {
let currentPath = path.resolve(dirPath);
while (true) {
const filePath = path.join(currentPath, '.graphqlrc');
if (fs.existsSync(filePath)) {
break;
}
if (isRootDir(currentPath)) {
break;
}
currentPath = path.dirname(currentPath);
}
return !isRootDir(currentPath) ? currentPath : null;
}
function isRootDIr(path: string): boolean {
return path.dirname(path) === path;
} As there are several use cases, we can tackle each of them and present a way to use this configuration programmatically. I can begin by identifying/suggesting some of them below:
Action itemsThere are many things to do! But when we successfully deliver this, I believe we can get to the point where we have a shared GraphQL configuration that everyone agrees on, with commonly-used techniques and best practices to configure your GraphQL environment.
Lastly, although we've begun this proposal and made a bunch of suggestions, I'd love to have a collaborative effort in pushing this forward. It'll be amazing to see us working together to improve and deliver this proposal to the GraphQL community. Please post your thoughts/concerns/questions! Also if you'd like to be responsible for one of the actions items above, don't be hesitate to do so :D |
@asiandrummer @wincent can you explain how eg. For me I'm afraid that if we somehow introduce several export type GraphQLConfigurations = {
[appName: string]: GraphQLAppConfig
}; ... we kill simplicity of consuming this config by tools. I'm sure that tools will not support multiapp configs. At least my plugins definitely will not support multiapps. I such disputable things I like to follow YAGNI principle. Populated by @leebyron in this great talk. PS. @schickling @asiandrummer our comments become tooooo long. I think that needs to create a new branch and publish draft there. For keep changes in track. And splitting our discussion to several issues. |
@nodkz: YAGNI is a very useful perspective, but what may not be clear in the preceding discussion is that WADNI (We Actually Do Need It) already; at FB, we have a
This is a great question, and one that whose answer we should try to define rigorously. @asiandrummer may be in the best position to describe how disambiguation proceeds in this case, because he wrote the code, but he's out this week, so we could also just look at the code. |
Thanks, @wincent you provide some clarification for me: pointing to your code and inform me that @asiandrummer also FB employee. Now I understand that for now only So I should convince you to not use |
I think it might be best to have a separate repository outside of the actual code of the language service to keep the specification for the config file - is this that repository? |
Agreed. The language service is just a single instance of an implementation. I am not sure if it is this repository or another one, but even if it is not this repository, this seems like a good place to start trying to sync up. One thing that I don't think has been mentioned yet explicitly (although maybe it has in this long thread) which I think will be important is the following: If we want a common "core" set of configuration values that works everywhere, we need to very clearly delineate the boundary between the "core" part and the "extension" part. There should effectively be a "namespace" of properties that are part of the core, and which tools wishing to implement support for should do so in a consistent way. And there should be a "namespace" where tools can place tool-specific config and know that it won't collide with other tool-specific extensions or core config. For example, there might be a "graphql-language-service" section, an "apollo" section, a "relay" section, a "foo" section etc where these tools could place an arbitrary set of useful properties that may not be generally useful but which still make sense to be placed in the same I am not sure what the best way to demarcate these different sections are. My sense is that something like this would work:
Basically, anything under "extensions", grouped by tool name, would be free-for-all. If we're worried about collisions on tool names, we could do something more heavy-handed like reverse FQDN keys ("com.facebook.relay" etc) or we could simply have an informal "registry" here or elsewhere where people "claimed" a property name by adding it to a list on a wiki page (I think I prefer this latter solution). Anything outside "extensions" should be considered "core", which means that, if a tool accesses that property, it should do so in a way that is consistent with the meaning defined in the "spec" here. |
I think that's a great suggestion. Keeping the core small would be ideal and then individual tools can iterate quickly on extensions. One more thing is that JSON is quite limited in stuff like splitting configuration across multiple files, etc. But the problem with |
What are your thoughts on using YAML instead? Many tools like Serverless are using it and it can be quite expressive. In general I really like the idea of |
I think YAML is pretty readable, and in some ways easier to write, compared to JSON. There may be some precedent for tools being agnostic about whether stuff is stored in YAML or JSON, although I can't remember specific examples right now. I do like the idea of starting with a declarative format that can be consumed from any language (not
At least to begin with, I think it's probably safest to start narrow and assume/require JSON, but consider extending to include YAML support once some of the above questions have been resolved. |
So obviously this is a lengthy thread, but it wasn't clear to me if the above proposals are suggesting that we do away with custom headers on requests to fetch a schema from an endpoint. Could someone clarify what the thoughts are on that? |
@mac-: My read of the situation with headers is this:
So I am open to the idea of adding them, but think we should proceed with caution. |
Thanks @wincent that helps. I agree that the spec should be kept small and that storing auth headers in a |
Sorry for the late response! As @wincent mentioned I was on a break, but I'm back in business ;)
There's an util function to find a correct app config by looking at the file path and determine if one of the input directories specified contains that file. I commented on graphql/graphql-language-service#15 (cc @nodkz), but I've put in a hard assumption that all inputDirs and excludeDirs are mutually exclusive - that is, each app contains a distinct set of directory paths to each other, and this notion directly contradicts the first case.
Although I'm still convinced that we would have to consider a monolithic repository with multi-apps configuration, I agree that it's a bit premature to push towards a generalized approach for it at this point, especially without having seen how others would use this proposal. I did want to reiterate that the multi-app use case is sometimes inevitable (i.e. an organization with pre-existing monorepo structure wants to try out GraphQL without major refactoring), but again we can come back to this later I think. I've opened #20 to track the proposal draft progress - let's continue this discussion over there! |
@mac- @akre54 Obviously I'm lacking an exact context to this as well as how certain babel plugins work, and the linked resource + this additional post about authentication target specific use cases - I still think/recommend we solve the authentication problem outside of the configuration level. If you absolutely cannot move away from having custom headers/tokens in the config file, I think |
ok this thread is super long now - I'm going to close this as the proposal discussion has moved to #20 :D |
@schickling and a group of contributors including myself discussed how a common GraphQL configuration would look like, and thought of redesigning the current implementation of
graphql-config
as well as including some new suggestions. Since GraphQL-powered environments usually include information about the common set of GraphQL artifacts (such as the location and specifications within the application directory), the idea is basically to recommend a commonly-used format/options within.graphqlrc
, and provide some helper methods and use cases from other applications that already incorporate this concept.A minimal
.graphqlrc
config file would consist of a JSON blob such as:To support more use cases,
.graphqlrc
would have a potential to evolve into something a bit more complicated. For instance, user might need to specify which sub-directories to look into for.graphql
files and other GraphQL-related artifacts. Also,schema-path
can be a local path to the GraphQL schema definition within the application, or an URL to the endpoint that would be used to fetch GraphQL introspection.To list these capabilities below:
In a pseudo-flowtype format:
Note that these are not at all requirements - these specifications, I believe, will serve as an example for how to set up configurations for various GraphQL app use cases. From the above format I've suggested all config properties to be optional except for
schemaPath
.@wincent and @jimkyndemeyer made a great suggestion that we provide some helpful tools/guidelines related to the configuration details we're proposing. Some examples are described below:
.graphqlrc
file programmatically from the top-level/sub-directories?schema-path
?.graphqlrc
approach?Finally, we're planning to propose this concept as one of "Best Practices" in graphql.org once it matures.
Please feel free to add/let me know if I've missed anything from our conversation @schickling and @wincent! I'm looking forward to see how this shapes up.
The text was updated successfully, but these errors were encountered: