Connectome-Implementation-Team / rescs_shacl_shapes

SHACL Shapes for RESCS
GNU Affero General Public License v3.0
1 stars 0 forks source link

Introduction To The Connectome GraphQL Endpoint #56

Closed tobiasschweizer closed 9 months ago

tobiasschweizer commented 1 year ago

Intended Use and Limitation

The Connectome GraphQL endoint is available for testing purposes. The GraphQL endpoint is still in development: its methods and response formats may change at any time. We would very much appreciate your feedback and suggestions for improvement.

Quick Start

You can use any GraphQL client like the Desktop app GraphiQL client or the web app https://www.graphqlbin.com/ with the Connectome GraphQL endpoint for testing.

You can even create a React client app based on the GraphQL schema, see https://github.com/Connectome-Consortium/react-graphql.

Concept

The Connectome GraphQL model is based on RESCS. If you acquire a basic understanding of RESCS, then the GraphQL model should be easy to understand.

The RESCS model is a selection from schema.org. All object types are derived from schema:Thing. Each object type inherits the properties from its super-type, e.g. schema:ScholarlyArticle inherits from schema:CreativeWork and from schema:Thing.

The Connectome GraphQL Schema

If you are not familiar with GraphQL, you should consult the official docs at https://graphql.org/learn first or check out one of the many tutorials, see https://www.howtographql.com/ for instance.

To explore the schema, consult the docs from your preferred GraphQL client.

Thing Interface

The types defined in the schema follow the hierarchy expressed in RESCS. All types implement the interface Thing. Thing is never instantiated. You will always deal with a type that implements the Thing interface, e.g., a ScholarlyArticle or a ResearchProject.

Pagination

For methods that return collections, the relay paging mechanism is implemented. You can think of it like as a wrapper around the method's response. Here is an example for an article search:

query {
  searchArticle(query:"artificial intelligence", first: 10) {
    totalCount
    edges {
      cursor
      node {
        name
      }
    }
  }
}

Here, we are looking for ScholarlyArticles about artificial intelligence. The parameter first sets the maximum size of the response. The query returns a PaginatedScholarlyArticle which means that there is a wrapping structure of the following shape:

edges: [ScholarlyArticleEdge!]
nodes: [ScholarlyArticle!]
totalCount: Int!
hasNextPage: Boolean!

A ScholarlyArticleEdge has the following definition:

cursor: String!
node: ScholarlyArticle!

You can access directly the nodes. However, using the edges as shown above has the advantage that you get a cursor for each result. The cursor can be used for pagination: just include it in the after parameter when doing the next request.

Other methods return different types, e.g., a ResearchProject. However, the structure of the wrapper is the same.

Union Types

Currently, there are five union types:

# Generic result type
union ResultUnion = ScholarlyArticle | Book | Dataset | Person | Organization | ResearchProject

# Result type for creative works, i.e. book, article, and dataset
union CreativeWorkUnion = Book | Dataset | ScholarlyArticle

# For fields that can point either to a person or organization, e.g., author
union PersonOrganizationUnion = Person | Organization

# For fields that are of type string or integer, e.g., pageStart
union IntStringUnion = IntBox | StringBox

# For fields that are an Iri or a string, e.g., a keyword
union StringIriUnion = IriBox | StringBox

The first is used if a methods may return more than one type, i.e. either type out of ScholarlyArticle, Book, Dataset, Person, Organization, or ResearchProject.

An example would be the generic search method:

# Definition of fields that should be returned for every result type
fragment baseFields on Thing {
  iri
  name
  description
  identifier
  sameAs
}

query {
  search(query: "energy", first: 30) {
    totalCount
    edges {
      cursor
      node {
        __typename

        ... on ScholarlyArticle {
          ...baseFields
          pageStart {
            __typename
            ... on IntBox {
              intValue
            }
            ... on StringBox {
              stringValue
            }
          }
          pageEnd {
            __typename
            ... on IntBox {
              intValue
            }
            ... on StringBox {
              stringValue
            }
          }
        }

        ... on Book {
          ...baseFields
        }

        ... on Dataset {
          ...baseFields
          keywords
        }

        ... on ResearchProject {
          ...baseFields
          abstract
        }

        ... on Person {
          ...baseFields
        }

        ... on Organization {
          ...baseFields
        }
      }
    }
  }
}

Since a result is of type ResultUnion, you have to use __typename to match the different types and set the fields, see the GraphQL docs. __typename also has to be used on the fields pageStart and pageEnd since page numbers may be either represented as numbers or strings, see type IntStringUnion.

Note that since every type implements Thing, you can use a fragment to avoid repetition when defining the fields to be returned.

Here is an example that shows how to deal with the PersonOrganizationUnion that is returned for the field author:

query {
  searchArticle(query:"artificial intelligence", first: 10) {
    totalCount
    edges {
      cursor
      node {
        name
        author {
          __typename 
          ... on Person {
            givenName
            familyName
          }
          ... on Organization {
            name
          }
        }
        inLanguage
        isAccessibleForFree
      }
    }
  }
}

Cardinality

In general, optional fields are nullable, i.e. the value null can be returned. If an optional field can occur at most once, then it is marked as nullable, e.g., description: String (no exclamation mark in the schema). If a field is optional, but can also occur more than once, then it is represented as a list, e.g., sameAs: [String!]. Not that the list itself is optional but if present, it always contains non-empty values, see the docs.