graphql / graphql-spec

GraphQL is a query language and execution engine tied to any backend service.
https://spec.graphql.org
14.31k stars 1.13k forks source link

Namespaces #163

Open OlegIlyenko opened 8 years ago

OlegIlyenko commented 8 years ago

At the moment all GraphQL types share one global namespace. This is also true for all of the fields in mutation/subscription type. It can be a concern for bigger projects which may contain several loosely-coupled parts in a GraphQL schema.

I tried to describe this feature in detail in following article (which also includes motivation and different use-cases):

GraphQL Namespaces Proposal

After posting this it, I saw interest from different people within the GraphQL community, so I decided go one step further and open this issue in order to suggest inclusion of this feature in the spec and start a discussion around it.

I suggested particular syntax & semantics for the namespaces, but I myself still not 100% sure that it can be used as-is. I hope, that as we discuss it, we would come up with better syntax and semantics (or maybe even entirely different feature) that better fits existing language features and covers described use-cases.

He-Pin commented 8 years ago

I think the / syntax is great ,with the current GraphQL spec,we need mount the on GraphQL ObjectType to another bigger GraphQL object as a field and a new name,like Mount Dummy type to a bigger graph as dummyService field.

And still I think https://github.com/facebook/graphql/issues/174 have something similar,just a little namespace.

leebyron commented 8 years ago

Thanks for writing this up, Oleg!

I'm a bit confused as to what this is enabling that isn't already possible or what the immediate goal of using namespaces would be.

I also expressly want to avoid namespaces being included and then people naively namespacing everything just because the feature is there, since that would result in a lot of bloated queries.

For example, in your post you explain that we could write queries like this:

{
  person(id: 12345) {
    id
    firstName

    schema.org/parent {
      givenName
    }

    schema.org/knows {
      givenName
      taxID
    }
  }
}

but I'm curious why you would prefer that over:

{
  person(id: 12345) {
    id
    firstName

    parent {
      givenName
    }

    knows {
      givenName
      taxID
    }
  }
}

The latter seems to represent the same thing, but just requires less typing. Is the goal to just describe what the fields relate to? Perhaps we could have metadata about the fields in the introspection system?

JeSuisNikhil commented 8 years ago

I think that the namespaces here enable one to "import" another schema. One could potentially import an schema and build on top of that rather than starting from scratch every time. Implementation wise it looks tough to do though. It might just be easier to import a library (js, ruby, python, java depending on the graphql implementation language) and then build schema using types available from that library.

Another reason why namespaces could be useful is so one can filter a introspection query with a namespace string. In a large system the number of types can make the introspection query results quite unwieldy. But then one could always write a custom introspection query that accepted a filter on a field that's common to all types.

I can think of another use of namespace which is that the number of top level fields (especially those with parameters) and mutations can become quite unwieldy and so one would have trouble viewing them in a graphql api browser like GraphiQL. Having top level fields and mutations further filtered by a namespace would make things easier on the eye.

OlegIlyenko commented 8 years ago

@leebyron Thanks a lot for looking into this and giving your feedback!

I think we can approach this from 3 different perspectives. First is a semantic meaning of types and fields. This is where schema.org example came from. I think for this one it should be enough to have a user-defined metadata on a field and type level which is exposed via introspection. I think community then can come up with conventions for how to encode this semantic information for use in different tools.

The second is the fact that the mutation (as well as subscription) fields share global namespace. Mutation type is the only place where mutation can be defined for a whole GraphQL schema. This is where my original motivation came from. I went into details about this particular point in the original post ("Mutation Type" section).

There are several factors to why I see it as a concern. At the moment mutation type should be an ObjectType, which means there that all mutation fields should be defined in it. This makes it hard to work with loosely-coupled parts of the schema which potentially may have name conflicts. In my opinion, one of the contributing factors is also the lack of polymorphism in input objects. This greatly limits amount of logic one can encode in one particular mutation field, which means that different variations of updates need to be represented as top-level mutation fields.

Namespaces can provide a solution for this particular problem, but I'm pretty sure that there are also other approaches we can take. For instance, let's imagine that it is possible to use a union type as a mutation type:

type AuthorMutation {
  create(firstName: String!, lastName: String!): Author
  changeName(id: String!, firstName: String!): Author
  delete(id: String!): Author
}

type ArticleMutation {
  create(title: String!, authorId: String!): Article
  changeText(id: String!, text: String): Article
  delete(id: String!): Article
}

union Mutation = AuthorMutation | ArticleMutation

schema {
  mutation: Mutation
}

a query like this would not make much sense because field name is ambiguous:

mutation {
  create(firstName: "John", lastName: "Doe") { id }
  delete(id: "1234") { id }
}

but we can use an inline fragment to disambiguate it:

mutation {
  ... on AuthorMutation { 
    create(firstName: "John", lastName: "Doe") { id }
  }

  ... on ArticleMutation { 
    delete(id: "1234") { id }
  }
}

If we provide a shorthand syntax for inline fragments, it can become a bit more cleaner:

mutation {
  AuthorMutation/create(firstName: "John", lastName: "Doe") { id }
  ArticleMutation/delete(id: "1234") { id }
}

This code violates the semantics of inline fragments and union types - they normally represent 2 mutually exclusive fields, still I use them here just to disambiguate the field names, so both mutation fields would be resolved. For the sake of example I use this approach simply because there is already syntax available for it in the language. The point I would like to make is that this example does not involve namespaces, still it addresses my issue with the global namespace for the mutation fields.

The third perspective is the the global namespace for the types. I recently had a few conversations with different people who are thinking on introducing GraphQL in their projects and companies (from small to pretty big). One question that came up several times in the discussion is whether GraphQL should serve as a facade for different internal services managed by different teams and if yes, then how does DDD fit into this picture. I do believe that GraphQL is a perfect fit for a facade API. Many client applications often have cross-cutting concerns, so it is very helpful to provide a facade for these client through a single GraphQL API, especially if clients are constrained in some ways (like mobile clients or embedded devices). On the other hand I find it helpful to think about different internal services as a bounded contexts. Each bounded context comes with it's own ubiquitous language. Given a system designed with these principles in mind, name conflicts are actually desired, but ubiquitous language within different bounded contexts can define very different semantics for the same terms (like User, Account, Product, etc.). I don't want to say that it's the best way to model system or anything like this, it's just some people use it and find it helpful for their own systems (including myself). This is where namespaces can be a very helpful tool when designing a GraphQL facade that aggregates several bounded contexts. I would be really interested to know your opinion on this topic. Do you think that GraphQL is suitable as facade in these kind of systems and if yes, then will you try to define the single model for all bounded contexts or you will try to preserve the ubiquitous language of every bounded context and minimize a common/shared area of the schema? (/cc @dschafer, @schrockn)

I also expressly want to avoid namespaces being included and then people naively namespacing everything just because the feature is there, since that would result in a lot of bloated queries.

Indeed, I do share this concern. I guess if one asks himself whether he/she needs to use namespaces, then the answer is probably no. On the other hand, as I described above, namespaces may be a useful tool to solve particular challenges (although, not the only possible tool in every given case).

The latter seems to represent the same thing, but just requires less typing. Is the goal to just describe what the fields relate to? Perhaps we could have metadata about the fields in the introspection system?

Indeed, in this particular example query I would normally skip the namespaces. This is the nice thing about the namespaces in contrast to naming conversions and other namespace emulation techniques which have quite a few disadvantages: even in presence of namespaces one can just skip them if they are not necessary (some implicit namespacing rules will apply in this case). There are also a few reasons why I may use explicit namespace in this query:

That said, I don't really want push concept of namespaces per se. My main goal is to address some of the issues, like global namespace for a mutations fields. Whether it is solved with namespaces or not does not really matter. I hope what I described here reveals a bit my original motivations for this proposal :)

aschrijver commented 8 years ago

Hi @OlegIlyenko ,

Nice elaboration and spec you`ve written down!

In general I see a clear need for a namespaces mechanism. I am currently writing microservices that communicate using graphql. With microservices architecture each microservice represents a separate bounded context (in DDD terminology) and you can easily get type conflicts when there are many of them around. I.e. same name, different semantics, etc. Also - like you said - there will be a need for common (base) types to be defined.

Your proposal certainly has its charms, especially the notation. Yet I also have a number of reservations:

In another issue I proposed a more general metadata mechanism, that is an extension and not breaking. See https://github.com/facebook/graphql/issues/200 The namespace could be defined in this metadata using any standard mechanism that is around (well, there should be some compatibility with JSON actually, but still). The structure of the metadata is defined like any type and there could be introspection support with a __METADATA type.

Using this metadata proposal for namespaces, however, is far less powerful than what you propose. But it can be adjusted. What I like in this is the freedom it leaves to implementers. Having metadata support we could wait and see how the community use it, what metadata formats are popular, before adopting a specific approach (Hell, there could be a Metadata Profiles feature in future and a 'marketplace' for profiles and other extensions. Yeah more marketplaces..)

Finally, I would be in favour of having a lean and mean base spec, and then have an extension mechanism that allows you and others to offer the kind of cool stuff you propose. It could be a standard extension. And I could consult a server to see if it supports this feature before sending my query (Note this is similar to the extension mechanism in OASIS CMIS spec and others).

Arnold Schrijver

yarax commented 8 years ago

Just a note: I'm currently writing a wrapper around existing REST approach based on microservices (with plenty of endpoints) and namespaces are very wanted, otherwise all type names look like fact_reports_pp_campaigns_delivered

domkm commented 8 years ago

I would also appreciate namespaces for all of the reasons stated above by @OlegIlyenko.

@leebyron Given that Facebook has nearly 10,000 types, how does it keep track of type names and query/mutation fields (which I am assuming there are hundreds if not thousands of as well)? Are there any best practices for merging schemas from multiple sources?

xuorig commented 8 years ago

As our mutation root grows we've been asking the same questions internally.

Given that Facebook has nearly 10,000 types, how does it keep track of type names and query/mutation fields (which I am assuming there are hundreds if not thousands of as well)?

Anyway you can share alternative solutions or insight on how you've been handling that problem @leebyron šŸ™ ?

smolinari commented 8 years ago

This might be totally off the wall and possibly even a stupid comment showing my lack of understanding, but couldn't different URLs and a routing middleware be used like namespaces? One could split up a larger app among different endpoints that way.

So, a bigger site would have

https://some-site.com/user https://some-site.com/blog https://some-site.com/admin

etc.

And if things are getting too involved even with this "namespacing", you could also break it down more.

https://some-site.com/user/profile (or register) https://some-site.com/blog/page (or /gallerie) https://some-site.com/admin/blog

I know, I know. We are heading towards something RESTy-like again with that idea, but it isn't really REST, right?

This might not solve the microservice communication issue, which I believe this idea came from. But, I think it solves the "big schema" and "name collisions" issues, since each endpoint would have its own schema. You'd also get the decoupling, which was asked for. It would allow for better asynchronicity too, because you could hit different endpoints at the same time, if needed, which I don't think would be too often. It would allow for parallelism, because breaking up into separate endpoints, theoretically, would allow a system to be scaled out much more easily later on too. And, if you refine the URLs, it isn't a breaking change.

The cons would be duplication. But really, if an app gets that big, I would imagine creating schema would be automated in some way anyway, right? Or does Facebook really have their devs go into the schema and alter or add to it all the time? I find that hard to believe.

I'm sure this blows away some other general concept that makes GraphQL so great, which I am completely missing due to my own lack of understanding. So, please do give me hell. All I can do is learn. šŸ˜„

Scott

khamusa commented 7 years ago

I'd like to note that allowing namespaces to be optional as long as they're unambiguous may difficult future developments of the schema, since you cannot safely add new types that would add namespace ambiguity having already allowed clients to write unnamespaced queries.

Regarding syntax, / seems an interesting choice, but I'd also propose . as a separator (in my opinion it is slightly less cluttering).

IvanGoncharov commented 7 years ago

As @khamusa point out:

you cannot safely add new types that would add namespace ambiguity having already allowed clients to write unnamespaced queries.

So you force all consumers of your API to bloat their queries by prefixing every type and field. I really like the idea that GraphQL force you to define a consistent vocabulary for your system.

And the biggest concern I have is that people starting to auto-generate one huge GraphQL proxy to expose a bunch of microservices without even try to reuse common types. And instead of solving communication problem in your organization you forcing your API clients to work around this by using prefixes and reading a ton of docs to understand the difference between: schema.org/Person, Accounting/Person, CRM/Person, etc. Moreover, I imagine a lot of developers will abuse this to implement versioning and I don't want to constantly use v1, v2, v3 namespaces.

It certainly possible to do the same stuff right now by making v1_Accounting_Person but it looks ugly and not encourage by any existing GraphQL feature.

Finally, as it was pointed out previously Facebook has nearly 10,000 types without any name clash. And I don't think many other API will ever approach this number.

leebyron commented 7 years ago

@IvanGoncharov illustrated a number of API design abuse patterns that worry me as well.

I've still yet to hear a compelling argument for why namespaces are necessary. The only argument I've heard so far is naming collision. Frankly, that's been a non-issue for us at Facebook and our schema continues to grow well over 10,000 types.

We avoid naming collisions in two ways:

  1. integration tests.

We don't allow any commit to merge into our repository that would result in a broken GraphQL schema. This test is pretty simple and fast to run, we just boot the server and if it fails to boot with a GraphQL error, we fail the test. Two types or fields on a type of the same name would cause such a failure. This kind of test ensures our master branch isn't constantly broken, and protects against name collision problems. If someone accidentally choose the same name for a new type, they can resolve the problem. Often, resolving it actually means using the existing type rather than renaming the new one. That keeps our API internally consistent and avoids duplicate information.

  1. Common naming patterns.

We have common patterns for naming things which naturally avoid collision problems. For example, mutations often follow a "verbNoun" pattern such as "likeStory" assuming that the noun is the type it operates on and your types also don't have conflicts as well, this naturally avoids conflict. I suppose with namespaces you could have written "Story/like" but that doesn't offer clients anything they didn't already have, and it might force them to handle multi-part names when reading the result. For types, we try to generalize as much as possible so that we have types shared across products. That makes future product integrations easier as well. For products that shouldn't integrate, or have similar concepts that are rightfully different, like a Facebook Event and an advertising logging Event, we alter the name to clear the ambiguity for the reader.

mvpmvh commented 7 years ago

Say I'm looking at your API in graphiql, I'm either a new fb hire or some dev looking to integrate with a part of your API. I pick 1 of your 10,000 types and I want to dig into the mutations that fall under this type. How do I, as a new hire or third-party dev, easily consume this information?

maciejkrol commented 7 years ago

For products that shouldn't integrate, or have similar concepts that are rightfully different, like a Facebook Event and an advertising logging Event, we alter the name to clear the ambiguity for the reader.

@leebyron How do you not end up with horrible caterpillar names like @xuorig said? I have not so very big schema and this is already a problem.

I tried to look for examples of big schemas or best practices. Nothing. Every article I could find is based on a schema with ~3 simple types. If you have anything please consider answering this question.

leebyron commented 7 years ago

Say I'm looking at your API in graphiql, I'm either a new fb hire or some dev looking to integrate with a part of your API. I pick 1 of your 10,000 types and I want to dig into the mutations that fall under this type. How do I, as a new hire or third-party dev, easily consume this information?

Typically our types do not correspond 1:1 with mutations. We typically do not have CRUD style mutations, instead we often have RPC style mutations that correspond to user events. In the cases that there is a closer relationship, the most common mutations are typically mentioned in the description of the type. For example the Story type includes a mention in it's description of the likeStory mutation, since it's a common question.

As I mentioned in the prior comment, naming conventions can help a lot. If you're looking at GraphiQL, the ideal experience is that you start typing what you expect to work and get typeaheads with helpful suggestions. In addition to this, both search in the documentation explorer, and helpful descriptions with recommendations for common mutations are all part of the strategy of providing information in the right places at the right time.

leebyron commented 7 years ago

@maciejkrol - our type system has many types, but with some clear and easy to follow naming conventions we balance between clarity, conciseness, and expandability.

The primary goal when naming things in a type system expected to expand in the future is to have clear answers for these two questions:

  1. Does this name introduce ambiguity? (e.g. if a type is called X, do you find yourself asking "what kind of X is this?")
  2. Can we imagine future expansions of this API in which this name will become too generic and therefore ambiguous? This question is much more difficult to answer, but forward-thought is always helpful when designing long-lived systems.

For example at Facebook we have a type called User, which in the beginning was clear via context that this meant a User of facebook.com. As our schema expanded, we introduced other types like InstagramUser, ThirdPartyUser, and other more specific types unique to certain circumstances or areas of the schema. Interface and Union types then help use related types together. For example, we have an interface called Actor which represents the entity which performed some action in the product, which is often a User but could be a handful of other types. We also have some type names that are longer and much more specific like EventsCalendarDateTime which belong to specific sub-products and have product-centric nuance that would be inappropriate for more generic shared types. In general this balance has worked for us: terse names for broadly used types and specific unambiguous names for specific or single-purpose types.

Cyberlane commented 7 years ago

@leebyron my only concern with your suggestions is that the Docs link within the GraphiQL will show a massive list of mutations under Mutation. Is there perhaps a way to decorate each item with some type of Category which will only affect the generated docs, whilst leaving the actual schema alone?

Our schema has two entry points, which is really nice.

Our mutations however are in the hundreds... and without being able to split them up a bit, the generated documentation becomes useless to our developer teams.

syrusakbary commented 7 years ago

@Cyberlane this issue might be related #252 :)

jhclouse commented 7 years ago

The ideal situation is one in which you have complete control over the design of your schema and can justify a well-crafted set of type names. Unfortunately, many of us are in a different situation: we have an ever-growing and ever-changing set of disparate backend data sources described with something like Swagger and would love to be able to import them on the fly without curating them. Maybe namespaces aren't the best idea. But it would be great to have some kind of support for this scenario.

isaksky commented 7 years ago

@leebyron For cases where you know the object you would like to mutate, but don't know what cutesy name your colleagues decided to give the mutation (updateStory, changeStory, likeStory), I think this would get annoying. Think about this: why do we organize our functions into modules and namespaces as programmers? Don't those reasons also apply here?

Also, what about allowing it only for top level fields? A lot of what I see in this thread looks messy/overkill, but allowing it just for the top level would solve most of the problem. Fields after the first level already have a context, so further namespacing after that may cause more problems than it solves.

KieronWiltshire commented 7 years ago

https://github.com/Quture/app/issues/1

This is exactly what I need for ^^

RickMoynihan commented 7 years ago

Hi,

I'm pretty new to GraphQL, so please take my comments with a pinch of salt, but I see the need for namespaces for some usecases, and I think there are several distinct reasons to want them.

  1. If you're generating your schemas dynamically for example from a database schema then the possibility of collisions is undesirable, and having to prefix fieldnames/typenames/enums etc is pretty ugly.
  2. More importantly without namespaces it's hard to see how it's possible to share schemas and target them to create portable clients across different services. Without them how do I know that the field droids contains the rebel droids I'm looking for? Namespaces would help turn schemas into contracts and enable extensibility and discovery.

You might say why do we need portable clients? But we already have the need for one as the graphiql IDE is used across many implementations and it appears to rely on __schema being a contract with the server. The use of the __ prefix here seems to be a tacit acknowledgement of the role of namespaces as __schema really means graphql/schema (to borrow the syntax of this issue). The problem is that __schema is effectively now reserved, embracing namespaces would be embracing 3rd party extensibility and would turn a closed system into an open one.

Namespace & module systems often allow importing a namespace as an alias, e.g. you could imagine something like the following (inspired by SPARQL), though I suspect you could find a prettier syntax:

ALIAS myns: <http://foo.com/mynamespace> 
ALIAS schema: <http://schema.org/>

{
  schema/name 
  myns/name
}

Unnamespaced names with the exception of __schema could then default to being in an anonymous namespace for backwards compatibility.

@leebyron's idea of putting this data into the introspection system sounds like an interesting proposal, though I'm not yet sure if it would be equivalent to first class namespace support.

thecaddy commented 7 years ago

This should happen. Lee may not support it but the standard is bigger than FB now.

šŸ‘

alloy commented 7 years ago

I donā€™t want to really double post and definitely donā€™t think my thoughts on the topic are crystallised well enough to actually partake in this debate, so for now I wanted to just link to some rough thoughts on this subject in the context of ā€˜stitchingā€™ schemas from various services https://github.com/apollographql/graphql-tools/issues/495#issuecomment-346607677

jlengstorf commented 6 years ago

@leebyron As more GraphQL tools are growing up around ideas like schema stitching and remote schemas, I think there's a compelling reason to look at namespacing again.

For example, the Apollo project is running into challenges with schema stitching that namespacing might be able to address more elegantly than existing solutions: https://github.com/apollographql/graphql-tools/issues/495

Another example is the idea of shared schemas. GrAMPS allows developers to create and share data sources as npm packages (e.g. https://github.com/gramps-graphql/data-source-xkcd), which means the solutions that work for a single organization (e.g. integration tests, naming conventions, internal communication) are no longer possible.

If Company A releases a data source that exposes their user management services, and that exposes a type like this:

type User {
  # ...user fields...

  "The userā€˜s personal details."
  details: Details
}

And that source is stitched together with a data source from Company B that provides a realty listing lookup with this type:

type Listing {
  # ...listing fields...

  "Additional details about the listing"
  details: Details
}

The developer consuming these two data sources at Company C has no ability to solve that problem.

However, developers could provide a namespace that's optional for most use cases:

namespace CompanyB

type Listing {
  # ...listing fields...

  "Additional details about the listing"
  details: Details
}

And this would allow the developer at Company C to work around the naming collision with something like this:

type HomeList {
  # unambiguous fields can be determined without namespaces
  owner: User

  # adding a namespace is only required if there is a collision
  listingDetails: CompanyB::Details
}

Right now GrAMPS is enforcing a prefix (e.g. all types are PFX_Details instead of Details), which works for now, but likely hits similar naming collisions in edge cases. Also prefixes are gross and noisy and I hate them. šŸ˜…

I'm definitely open to solutions other than namespacing, but I haven't been able to come up with anything cleaner than what I've described here.

What do you think? Is this worth reexamining?

acron0 commented 6 years ago

I think @jlengstorf is on the money, as are a lot of the replies here. "Naming conventions" is not a reliable solution to this problem and, as demonstrated, doesn't scale as soon as you consider remote schema.

Namespacing is the obvious and logical solution to this. It's a concept that's well understood by most language users and would provide a lot to GraphQL.

acjay commented 6 years ago

I really like Oleg's proposal. The namespacing of interface fields reminds me of ES6's symbol feature, which effectively allows different concerns to share the "property space" of an object without collisions. I also like the way in which these field name spaces can be included, in that it helps make a type's interface fields more clear. I think this concept is absolutely crucial to getting to a place where GraphQL can be used to build services that compose and transform other services without manually mapping schemas.

I'm sure this has been mentioned, but in most modern languages that have namespaces and packages, it's possible to totally omit them if you don't need the modularity. It seems like they could be added in such a way that users that don't need them could simply ignore the feature.

I think the following features would be really helpful:

isaksky commented 6 years ago

Regarding "namespaces only required if there is a collision": that is an extremely dangerous semantic, because it means adding a field can break queries, which I expect most people will agree is unacceptable.

SQL has this problem in a way, because when writing a query you elide giving tables an alias, and fail to qualify your column references with the alias, your query will break if a column is added that makes the column reference ambiguous. Not good.

I'm still in favor of namespaces, but we'd have to be careful to not introduce brittleness.

yordis commented 6 years ago

@leebyron what is the status of this?

Currently I am building a distributed administration tool which you have inject dynamically the available services, because of that, each service controls their GraphQL schema definition and this feature is something that we definitely need.

We tried to look up for Schema Stitching solution but it wouldn't work because the lack of namespace of GraphQL.

For example, having IAM and CRM services where each service has it own Account and they can't be merged together in the schema stitching (even if we couldn't we wouldn't, prefer to use prefixes).

So it will be nice if we could have something as proposed but to be honest I would prefer to use dot notations instead so you keep it as closer as possible to Javascript like syntax (but I dont care about that detail to be honest, whatever is faster and easier to parse)

like so

// system: IAM
type IAM.Account { ... }
// system: CRM
type CRM.Account { ... }

Services like Amazon, Google Cloud and my situation will benefit a lot form this because we could expose a single GraphQL endpoint that allow people to access to a whole set of tools but most of those tools are built distributed either from code perspective and from company process perspective.

GraphQL is entering more mature fields and by so sometimes the use cases become a little bit more complex. I really hope that this feature gets into the spec.

cruhl commented 6 years ago

The way I've gotten around this is automatically prefixing types. I imagine if this issue lingers, along with the lack of imports/exports, it will be really tempting to make some sort of SCSS-like GraphQL preprocessor. That actually sounds like an awesome project šŸ™‚

acjay commented 6 years ago

It would truly be a shame if we replicate the module/compositionality hell that HTML, JS and CSS took over a decade to escape.

yordis commented 6 years ago

@acjay I am not asking for upload any schema from any URL or something like that (that is a no-no) I just need to be able to specify a namespacing for my types. So what do you mean by that?

namespace IAM {
  type Account { ... }
}
namespace CRM {
  type Account { ... }
}

So you would refer to as

IAM.Account
CRM.Account

But definitely a no-no to be uploading those types from anywhere or something like that (like what happened with HTML)

rydrman commented 6 years ago

I have been rolling these many ideas around in my head lately for our internal APIs, and there's some really interesting suggestions and use cases being expressed here. I find that my team is facing many of the same problems and would definitely agree that namespaces in some form or another could easily solve a great number of them. What worries me a little about this kind of solution is that it seems to break some of the promises of graphql from the perspective of the API consumers (which I'll try to explain).

One of the important reasons that graphql was chosen for our internal API is that we represent different aspects of the same data across many systems, and need that data to be presented naturally to the API consumers. For example, our employees get accounts created across many different systems, but from the perspective of the api consumers, a user is simply one person with many attributes. If I intend to ask them to prefix or otherwise qualify their queries with the names of those systems, then I feel like I am doing them a disservice, and that my promise of a natural view of the data has been broken.

Something I have considered before is whether or not there could be a solution at the field level: merging conflicting types under a single one. That, obviously, creates a lot more problems than it solves but I think it presents in interesting perspective on the problem.

Maybe simply presenting two sets of data is not enough to create a truly excellent api, and there is a need to craft the solution more intentionally. Take it this way: we do not create new libraries by simply gathering the ones that we like in a bundle and calling it something new... instead we import what we need and we build something better, or at least something different. The apollo graphql tools for schema stitching touch on some of these ideas in their ability to link schemas, but maybe there is a need for a more first-class solution in the schema language for that kind of deliberate design... whatever that might look like :neutral_face:

acjay commented 6 years ago

@yordis I'm for your plan or something like it. I was saying it's a shame we wasted a decade faking namespaces poorly in those other web techs. Especially when it's really pretty simple to make a namespace/module system that's there when you need it, but out of the way when you don't.

@rydrman I think I can rephrase what you're saying: in a perfect world, GraphQL hides system boundaries as an implementation detail and just provides a fresh type space. I love that concept in theory, but in reality, I think it would involve painstaking manual composition of types and resolvers. But I also don't think it should be necessary to choose one or the other. A gateway API could present a consolidated schema, while still proxying underlying schemas in their namespaces. I think the former is ideal for applications to consume and the latter for prototyping and operations.

yordis commented 6 years ago

@leebyron @IvanGoncharov as core contributors I would like to have your inputs on this. Could you take sometime for try to move forward with this please šŸ™

IvanGoncharov commented 6 years ago

@leebyron @IvanGoncharov as core contributors I would like to have your inputs on this. Could you take sometime for try to move forward with this please šŸ™

@yordis I'm very flattered but I'm just a community member who is very active lately so you need to convince @leebyron.

My suggestion is that somebody should aggregate all feedback from this issue and formulate a concrete proposal(syntax, necessary changes, etc.) in a separate issue. Also, include answers to "Favor no changes" questionary: https://youtu.be/mePT9MNTM98?t=19m35s It would be way easier to give feedback on concrete proposal than on long thread of abstract ideas.

And don't forget to address @isaksky point:

Regarding "namespaces only required if there is a collision": that is an extremely dangerous semantic, because it means adding a field can break queries, which I expect most people will agree is unacceptable.

Plus you need to solve the same problem for type names since they also could be used inside queries (fragments, inline fragments, operation definitions).


As for my personal opinion, I already wrote it here: https://github.com/facebook/graphql/issues/163#issuecomment-271296026 I also read through the recent comments and here are my thoughts:

One of the important reasons that graphql was chosen for our internal API is that we represent different aspects of the same data across many systems, and need that data to be presented naturally to the API consumers. For example, our employees get accounts created across many different systems, but from the perspective of the api consumers, a user is simply one person with many attributes. If I intend to ask them to prefix or otherwise qualify their queries with the names of those systems, then I feel like I am doing them a disservice, and that my promise of a natural view of the data has been broken.

@rydrman You absolutely right, by forcing your API consumers to figure out correct namespace you are breaking one of GraphQL design principles: http://facebook.github.io/graphql/draft/#sec-Overview

Productā€centric: GraphQL is unapologetically driven by the requirements of views and the frontā€end engineers that write them. GraphQL starts with their way of thinking and requirements and builds the language and runtime necessary to enable that.

I think I can rephrase what you're saying: in a perfect world, GraphQL hides system boundaries as an implementation detail and just provides a fresh type space. I love that concept in theory, but in reality, I think it would involve painstaking manual composition of types and resolvers.

@acjay I think the main problem of creating consistent API is to figure out how different substems relates to each other and what are domain objects of your entire system. So if you just namespace your types you not solving this problem but just passing this work to your clients. For example, you either figure how to unify IAM.Account and CRM.Account into one or you forcing your frontend developer to do the same in order to present consistent UI.

As for technical issues with resolvers and type composition, it's the only half year or so since people started to work on tools for schema stitching and I already see a lot of progress in this space.

yordis commented 6 years ago

From query perspective (outside of namespacing the types)

{
  StrawHat { // this is just a namespace (business namespace)
    Map { // this is another namespace (a module of the business)
      country { id isoTwo }
    }
    // Many other modules used
  }
  // Many other businesses connected
  AWS {
    IAM {
      account { id, email }
    }
  }
}

That administration tool will allow you to configure which business and which modules of the business you want to connect to the administration.

With this,

You could write a IAM for Amazon AWS module as well ass IAM for StrawHat, without namespace, sure we can always have long names like

strawHatIamAccount(id: ID!): StrawHatIAMAccount
awsIamAccount(id: ID!): AwstIAMAccount

but as you can see the name is really hard to read

and also, every single query will under the root so the list could be really long root.

I am trying to find a clean solution to this issue without having to modify the spec but to be honest this is quite ugly from implementation perspective, what I would do is to come out with a hack that IAM, StrawHat, Map, IAM keys are dummy resolvers I guess (trying to figure out this one) but still the name of the next data type keep adding more and more prefixes because the names are global. Also, mutations are first level only so I will have long names mutations.

Also, I code stitching is fine when you have the capability to merge the types, this is not the case at all.

freiksenet commented 6 years ago

I've done some related work. Here is example using new graphql-tools Transform API to prefix schema types and root fields (which is practically namespacing).

While this doesn't really address namespacing problem, it at least provides practical solution for users consuming third-party APIs. I think in case one controls the whole API set in the product, it's possible to maintain the type uniqueness manually, but prefixing/namespacing is useful for easily consuming third-party APIs.

ronbravo commented 6 years ago

I just want to throw in that I am in favor of namespacing, I am very new to GraphQL, but namespacing seems like a generic development problem that comes up over and over again. Reminds me of the problems that came up with the lack of namespacing in JS language.

I have have to agree with @acjay and @acron0 as this seems to be a problem that has already been solved. I like @yordis syntax over the original proposed by @OlegIlyenko as it reminds me more of the dot notation already used in JS.

Also, seems that this kind of conversation has happened before. And the resistance was pretty similar. If you dig around on the net at the namespacing / scoping discussions around npm, it was argued much that there was no clear benefit or need. However, eventually scoped packages were introduced, and from what I understand it was not an easy thing to add? Obviously it's not exactly the same situation as being described here, but again going back to what @acjay said, seems this problem has been solved already in the past in many different ways.

Facebook has a solution that works for them and I understand much of the sentiment that one can still work within a global namespace context. For example, many C libs work in this sense (Gnome project for example), but the naming does tend to become a lot more verbose, as many have pointed out. In C that makes sense to me considering the verbose nature of the language, in JS not so much. Just my opinion.

The namespacing issue probably won't go away.

thehappycoder commented 6 years ago

Namespaces are great for cases when a single graphql server acts like a gateway for many other graphql servers. This will avoid possible conflicts during schema merging. See https://www.apollographql.com/docs/graphql-tools/remote-schemas.html for details.

cruhl commented 6 years ago

What's the latest news on this? I'm very excited to see what develops...

acron0 commented 6 years ago

Zero FB input since @leebyron and he's left now.

mjmahone commented 6 years ago

Some FB input: I think @leebyron covered it really well in the previous discussion.

Namespaces are not needed at FB, which indicates it's unlikely they are truly needed in the general case. We have one gigantic (now well over 10,000 types) schema. But in general people only work with a very small subset of the schema: it's easy to build tooling to find all fields that return a User on Query/Mutation, for example. If you need to "slice and dice" you schema, I recommend using strong naming conventions (i.e. Instagram before all Instagram-only types), and then build schema-exploration tooling that lets you filter by prefix.

The "gateway" idea is interesting, but I do not think needs to be built into the spec itself. It ought to be straightforward to create a "synthesized schema" on top of a set of specific schemas, which auto-namespaces for you. Similarly at Facebook we're encountering situations where we want to "swap out" fragments within a query based on which app is running. I would not propose adding our solution to this into the spec, as it involves building dependency tooling on top of the spec, but in the end has essentially nothing to do with how we send or parse responses over the network. Besides that our client needs to understand which version of the query it's actually parsing.

acjay commented 6 years ago

For a slightly more optimistic take, I think it's on someone to champion the idea with a spec and really clear semantics for how it works. It's a great opportunity to make a big mark on a tech that's gaining a lot of popularity.

acron0 commented 6 years ago

@mjmahone I'm not sure what kool-aid you're drinking but FB doesn't represent "the general case" and your use cases are quite obviously not universal.

leebyron commented 6 years ago

@acron0 that tone is not helpful. Please keep discussion civil. Also, I havenā€™t left.

I believe @mjmahone was trying to reiterate previous feedback that while namespaces were proposed to handle ā€œlargeā€ schema, Facebookā€™s is probably the largest by quite a lot and never felt any growing pains that suggested namespaces were necessary. I also appreciated his advice and feedback on the gateway concept.

As for news, all work is pretty well captured here. Iā€™ve applied the appropriate labels, but there hasnā€™t been much forward momentum on this concept in quite some time. The next steps are either to decide that GraphQL does not need namespaces and close this issue, or to have a champion take on this work and develop it to meet RFC 1.

cruhl commented 6 years ago

@leebyron I plan on creating a tool which "compiles" my module-enabled schema definition to spec-compliant GraphQL. Here's my initial plan...

module Time

scalar Date
scalar Duration

...would produce...

scalar Time__Date
scalar Time__Duration

You can import/reference modules like so...

import Time

type Event {
  start: Time.Date!
  duration: Time.Duration!
}

...or...

module Event

import Time (Date, Duration)

type Event {
  start: Date!
  duration: Time.Duration! # Qualified name still allowed
}

...or...

module Event

import Time as CoolTime (Date, Duration)

type Event {
  start: Date!
  duration: CoolTime.Duration! # Qualified name alias still allowed
}

Those would both produce...

type Event__Event {
  start: Time__Date!
  duration: Time__Duration!
}

I plan on following Elm's module schematics pretty closely. Is building this kind of usable-preprocessor tool a good start toward more serious discussion?

rmosolgo commented 6 years ago

I'm not leebyron but I'll answer anyways šŸ˜† I think YES that would be a great first step: you could demonstrate a working, valuable example as a starting point for the specification.

That's something I really appreciate about the GraphQL change process: only proven features are added, that way, we don't end up accidently adding complexity without any value.

So, if you prove the value and smooth out the bumps, that's a big step towards standardizing! Please do chime in here when you get that project started, I'd love to see how it plays out :)

acron0 commented 6 years ago

@cruhl I think some of your examples are wrong: start seems to be duplicated in the Event type.

xuorig commented 6 years ago

I've re-read this issue today to try and get a feel of why we want namespaces.

From what I'm reading, most concerns are in regards to type conflicts and ambiguity. I've been thinking a lot about GraphQL modules/composition/stitching/gateway/etc recently. I've also been interested in knowing how we can represent Bounded Contexts and preserve a ubiquitous language across different parts of the schema.

To me, whether your schema is distributed on n services or found in one big schema, there could still be a need for defining bounded contexts in the GraphQL Schema. You may have two representations of User within a single GraphQL schema just like you would if you had to merge a 3rd party schema or a schema from your other services. So in that sense, I don't think the argument that people are starting to use GraphQL in a distributed way is necessarily a great one for adding namespaces.

Now it also means I do see value in having something like namespaces, but as many have said before me, it is already possible to remove ambiguity and conflict using naming right now. The only downside I see right away with the approach is sometimes over-complicated or "uglier" names for types.

Having namespaces also brings a whole set of other questions. Can a field from one namespace reference any other type from other namespaces? Is there a concept of entry points to other namespaces? Can I have private types within a namespace? If not, it almost seems like an illusion of namespaces, to have better looking naming?

As far as stitching and conflict handling goes, I have some opinion that maybe the schema should not be distributed in this way. In fact I can see a gateway tool allowing something like this, it looks quite similar to what others have tried (cc @cruhl). Slightly different than auto-prefixing modules because depending on where your GraphQL module is used, maybe we'd like the gateway/merge point to decide on final names.

schema.graphql

# import "User" from "service-a" as "UserA"
# import "User" from "service-b" as "UserB"

type Query {
  userA: UserA!
  userB: UserB!
}

This example looks like an SDL extension, but it could just be code also. In this scenario, some services define their schemas, but the final schema is generated statically as a "monolithic" schema using imports.

So basically, I've yet to see something that needs to be in the spec, besides the argument that naming becomes a little bit less optimal than if we had some kind of namespace notation. I see value in namespaces but I'd like to see whether they would have other functions. I can see that a spec maintainer would prefer the status quo here since almost everything seems possible right now given the right tooling, and adding such a feature to the spec comes with huge implications. To me the balance is still a bit more on the status quo side right now.

Maybe my thinking comes from using a "Schema as Code" approach for so long vs the very used SDL first approach out there, but I've always had no problem using code / extensions and treating the GraphQL Schema or SDL as an artifact/result of the schema build. Maybe that's why some of us see more value in namespacing if the SDL is used directly as the way to build a GraphQL API.