Skip to content

Thought: avoiding self-repitition when a schema is defined as a string #230

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

Closed
kachkaev opened this issue Nov 30, 2016 · 1 comment
Closed

Comments

@kachkaev
Copy link
Contributor

kachkaev commented Nov 30, 2016

I quite like the idea of defining schemas simply as strings – it's great that it can be a part of the git repo in such a form. This makes the schema readable by any member of the team without running a server and also enables the new features to be tracked even by those who is not great in server-side JS (git diff for a spec looks pretty straightforward).

A problem being introduced by this approach is that some parts of the schema become a bit more verbose than needed, e.g. when some set of fields is shared between an interface and derived types:

export default /* GraphQL */`
interface Character {
  id: ID!
  name: String!
  friends: [Character]
  appearsIn: [Episode]!
}

type Human implements Character {
  id: ID!
  name: String!
  friends: [Character]
  appearsIn: [Episode]!
  starships: [Starship]
  totalCredits: Int
}

type Droid implements Character {
  id: ID!
  name: String!
  friends: [Character]
  appearsIn: [Episode]!
  primaryFunction: String
}
`;

When I wrote my first schema in a text form, I intuitively expected this to work:

export default /* GraphQL */`
interface Character {
  id: ID!
  name: String!
  friends: [Character]
  appearsIn: [Episode]!
}

type Human implements Character {
  starships: [Starship]
  totalCredits: Int
}

type Droid implements Character {
  primaryFunction: String
}
`;

As probably guessed, this was not very successful :–) BTW /* GraphQL */ just helped Atom's language-babel plugin nicely highlight the types.

On the yesterday's GraphQL meetup in London I shortly discussed this problem of self-repetition with @martijnwalraven and he explained me why it cannot be solved with the concept fragments. Turns out fragments are designed to be used only on the client and their main purpose is not to reduce the number of the lines of code, but to perform some cool magic.

However, I still think that there's something that may be done in the server-side app to avoid too much repetition. It does not necessary have to be a part of the GraphQL spec – just some basic ‘syntax sugar’ will do the job (our purpose is just to make the schema definition readable by everyone including product managers).

The first thing that comes to mind is some form of a ES6 spread operator (which implements a simple string.replace() under the hood). A quick sketch:

import withSchemaMixin from 'graphql-tools/with-schema-mixin';

const withCharacterFields = withSchemaMixin('characterFields', /* GraphQLMixin */`
  id: ID!
  name: String!
  friends: [Character]
  appearsIn: [Episode]!
`);

export default withCharacterFields/* GraphQLWithMixins */`
interface Character {
  ...characterFields
}

type Human implements Character {
  ...characterFields
  starships: [Starship]
  totalCredits: Int
}

type Droid implements Character {
  ...characterFields
  primaryFunction: String
}
`;

This may work not just for the sets of fields, but also for certain collections of commonly used attributes. Mixins (or whatever this thing may be called) can be easily made composable.

import composeSchemaMixins from 'graphql-tools/compose-schema-mixins';
import withSchemaMixin from 'graphql-tools/with-schema-mixin';

const withLocaleArgument = withSchemaMixin('localeArgument', /* GraphQLMixin */`
  locale: Locale = EN
`);

const withTimeIntervalArguments = withSchemaMixin('timeIntervalAruments', /* GraphQLMixin */`
  from: Timestamp,
  to: Timestamp
`);

const withProductModelFilterArguments = withSchemaMixin('productModelFilterArguments', /* GraphQLMixin */`
  manufacturerIds: [ID!],
  departmentIds: [ID!],
  tagIds: [ID!]
`);

const withStandardEntityFields = withSchemaMixin('standardEntityFields', /* GraphQLMixin */`
  createdAt: Timestamp!
  modifiedAt: Timestamp
  isDeleted: Boolean
  name(...locale): String
`);

export default composeSchemaMixins(
  withStandardEntityFields,
  withLocaleArgument,
  withTimeIntervalArguments,
  withProductModelFilterArguments,
)/* GraphQLWithMixins */`
scalar Timestamp

enum Locale {
  DE
  EN
  RU
}

type ProductModel {
  ...standardEntityFields
}

type RetailPoint {
  ...standardEntityFields
  description(...locale): String
  productModelsInStock(...productModelFilter): [ProductModel!]!
  productModelsInStockCount(...productModelFilter): Int
}

type Customer {
  ...standardEntityFields
  name: String
  productModelsInCart(...productModelFilter): [ProductModel!]!
  productModelsInCartCount(...productModelFilter): Int!

  productModelsOrdered(...timeIntervalFilter, ...productModelFilter): [ProductModel!]!
  productModelsOrderedCount(...timeIntervalFilter, ...productModelFilter): Int!

  #...
}
`;

Thinking further, these mixins may be made smarter than just bare-bone string.replace() and even work as true spread operators. In type Customer name would turn from a locale-aware filed to a normal one. But that's probably a bit too hard conceptually for a start.

Although I see certain benefits in this extra layer of abstraction, I understand that it comes at a cost, which is first of all the existence of the layer of abstraction as such. Besides, with the introduction of these mixins in the schema definition file, the resolvers might need to get something similar as well.

However, at the moment I see more pros than cons in the overall idea, especially in the long run. As the GraphQL tooling improves further, server developers will probably get more focused on the schema than on the underlying layer with resolvers, so keeping things shorter and thus more human-readable can help many teams.

I'm curious to know what the community thinks of this extra ‘syntax-sugar’ idea for Apollo's graphql-server. Feel free to criticize it as much as you want – it's pretty raw after all! :–)

@stubailo
Copy link
Contributor

stubailo commented Dec 1, 2016

I think you probably might want to repost this here: https://github.com/apollostack/graphql-tools

graphql-server doesn't do any of these things, it's graphql-tools. Reopen and let's talk there?

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants