Data modeling

Cohere

Micrograph uses cohere for modeling data.

cohere is a simple schema lib that is concerned with attributes and relationships. cohere supports "bring-your-own-typing" for attributes (such as GraphQL scalar types). Micrograph requires that you use GraphQL scalar types for attributes, but if you choose to use cohere for a different project, you can use arbitrary typing (or none at all).

npm install --save cohere

Relationships

cohere is opiniated with relationships. It supports hasMany, belongsTo, and hasOne.

hasMany(type, inverse, options)

The hasMany relationship is used for M:1 or M:N relationships. For example, a user might have many blogs. In another example, a teacher might have many students and vice versa.

import { GraphQLString } from 'graphql';
import Schema, { hasMany, belongsTo, hasOne } from 'cohere';
import { Blog, User } from './models';

export default new Schema()
  .defineType('user', {
    attributes: {
      name: GraphQLString,
    },
    relationships: {
      blogs: hasMany('blog', 'author', {...}),
    },
    model: User,
  })
  .defineType('blog', {
    attributes: {
      title: GraphQLString,
    },
    relationships: {
      author: belongsTo('user', 'blogs', {...}),
    },
    model: Blog,
  })
  .compile();

For example, a GraphQL query might then look like:

query getUsersBlogs {
  fetchUser(id: "1") {
    name
    blogs {
      id
      title
    }
  }
}

query getBlogsAuthor {
  fetchBlog(id: "1") {
    title
    author {
      name
    }
  }
}

belongsTo vs. hasOne

Both belongsTo and hasOne are for modeling 1:1 and 1:M relationships. In the example above, the blog type belongs to a user type via the author field.

Micrograph will generate an author field on the Blog type that points to a User type.

What's the difference between belongsTo and hasOne?

Nothing, really. They both represent 1:1 relationships, and cohere includes both mainly for use by the database layer. For example, we typically use belongsTo on a piece of data that we consider to be dependent. In other words, the existence of a blog depends on the existence of a user. A blog cannot exist without an author. In contrast, a user might have a company relationship:

...
relationships: {
  blogs: ...,
  company: hasOne('company', 'employees', {...}),
},
...

In this example, it doesn't make sense for a user to belong to a company because a user might be unemployed.

Micrograph handles belongsTo and hasOne relationships identically, and their use cases are left to the developer. For example, you might use belongsTo or hasOne to specify which table has the foreign key, how cascade deletion works, or which NoSQL collection has an index.

Resolving relationships

Micrograph expects every model to have a corresponding instance method that matches the relationship's name. For example, if a user has many blogs, then the User class should have a blogs method.

import db from './database';

class User {
  constructor(data) {
    this.id = data.id;
    this.name = data.name;
    ...
  }

  blogs(args, ctx) {
    return db.blogs.find({ author: this.id });
  }
}

If a schema type declares that it has a certain relationship, but the class does not have an instance method with a matching name, Micrograph will always resolve that relationship as null.

Overriding a relationship's output type

Micrograph creates a [Blog] type automatically for hasMany relationships. You can override the output, however. You can do things like have support Relay connections:

// schema.js
...
schema.defineType('user', {
  attributes: {
    name: GraphQLString,
  },
  relationships: {
    blogs: hasMany('blog', 'author', {
      output: createConnectionType,
    });
  },
});
...

You can also transform the relationship data right in the schema without having to mess with your business-logic layer. So, if the User class's blogs method returns a normal array, you have an opportunity to transform it into the shape required by your output key:

// schema.js
...
schema.defineType('user', {
  attributes: {
    name: GraphQLString,
  },
  relationships: {
    blogs: hasMany('blog', 'author', {
      output: createConnectionType,
      transform: connectionFromArray,
    });
  },
});
...

Just like root queries and root mutations, you can specify args for each relationship:

// schema.js
...
schema.defineType('user', {
  attributes: {
    name: GraphQLString,
  },
  relationships: {
    blogs: hasMany('blog', 'author', {
      output: createConnectionType,
      transform: connectionFromArray,
      args: connectionArgs,
    });
  },
});
...

The blogs relationship is no longer a [Blog] type. It's now a BlogConnection type. Check out an example Relay app.

results matching ""

    No results matching ""