JSON Definition Guide

Graviton has supports a powerful way to generate many services using the json model from json-schema.

Definitions and Bundles

It shares this model with internal components we use to load data into some graviton services and this is it'’s main documentation.

JSON definitions usually get installed through bundles. The simplest case of such bundles being bundles that only contain some definitions.

For graviton to pick up json definitions they must reside in a Resources/definition folder anywhere in the bundle.

Inside a Definition

Each definition defines either a full services or subdocument parts of a service. For starters they must name and describe the thing they are creating as well as ensuring it has a unique id.

{
  "id": "foo",
  "title": "Foo Resources",
  "description": "Foo resources get used at Acme Inc. to create their deliverables."
}

The remainder of the definition is split into two parts. The service subnode describes most of the aspects of how each services gets rendered. The target entry describes how the underlying mongodb insrtance is to be set up.

{
  "service": {
    // ...
  },
  "target": {
    // ...
  }
}

We will be looking at each of those sections individually starting with the service section.

Service

At the top level of a service some basic aspects of the service are set up. The routerBase defines where on the service a service should be created. And the readOnly attribute defines if PUT/POST/DELETE routes for a service are to be exposed.

{
  "service": {
    "routerBase": "/acme/foo",
    "readOnly": false
  }
}

The foo resource service from above shows up on http://example.com/acme/foo/ and exposes a fully editable service with the above.

Info

Creating read-only services is not (yet) covered by this guide.

Target

For the service to be usable it also needs to define some fields in the target entry.

{
  "target": {
    "fields": [
      // ...
    ]
  }
}

Since we want Acme Inc. to be able to categorize their foo resources we add an array of tags. So that Acme Inc. can use their widgets in internationalized software we make sure that the title of the resource is translatable. We are also adding an id field since we want to be able to access our resources and reference to them.

{
  "target": {
    "fields": [
      {
        "name": "id",
        "type": "varchar",
        "title": "Foo Stock ID",
        "description": "Unique stock idintifier for foo resources as needed by existing systems."
      },
      {
        "name": "title",
        "type": "varchar",
        "title": "Name of resource",
        "description": "Human readable name of foo resources."
        "translatable": true
      },
      {
        "name": "tags.0",
        "type": "varchar",
        "title": "Tags",
        "description": "Tags to easily categorize foo resources."
      }
    ]
  }
}

As you can see each of the fields have some distinct fields that need setting.

Fields

After having introduced the field array it makes sense to look into how fields are defined.

For the most part the fields show above are what is required on all fields. The name tells graviton how the field should be exposed in the resource. The title and description field get exposed in our JSON-schemas and in our swagger spec.

The type specifies what type of field that graviton shall realize. It supports multiple options.

For a start the following values are all legal.

  • string
  • varchar
  • text
  • int
  • bigint
  • float
  • double
  • decimal
  • datetime
  • boolean
  • hash
  • object
  • extref

For the most part they do what one would expect them to. Please refer to the JSON definition reference for more info.

For referencing other services the type field also supports referencing classes in the following forms.

  • class:AcmeNamespace\\AcmeBundle\\Document\\Bar
  • class:AcmeNamespace\\AcmeBundle\\Document\\Bar[]

Special fields

There are some special cases of fields that need some more work. They are explained here.

Translatable fields

You can define fields as translatable. They will then get rendered in all the languages available in the system.

{
  "target": {
    "fields": [
      {
        "name": "translatableField",
        "type": "varchar",
        "title": "Title of translatableField",
        "description": "Description of translatableField in a sentence.",
        "translatable": true
      }
    ]
  }
}

Please keep in mind that for this to work the default language (English) always needs to be defined.

You only need to set translatable to true on fields you need it. The default is to not make fields translatable.

Extref fields

Albeit there name, extref fields are used to reference other services in a graviton instance using JSON-Reference.

{
  "target": {
    "fields": [
      {
        "name": "refField",
        "type": "extref",
        "title": "Title of $ref",
        "description": "Description of $ref in a sentence.",
        "exposeAs": "$ref",
        "collection": ["*"]
      }
    ]
  }
}

You should only use the extref type with exposeAs set to $ref. You can specifiy what target are valid in a $ref field using the collection attribute. The above example allow links to anything, but you can also specify allowed mongodb collection names.

{
  "target": {
    "fields": [
      {
        "collection": [
          "Bar"
        ]
      }
    ]
  }
}

Dynamic key fields

There are some cases where we need to expose a array for objects as a hash, using a field from the object as key for the resulting hash.

{
  "name": "bar",
  "type": "class:AcmeNamespace\\BarBundle\\Document\\Bar",
  "title": "bar resources",
  "description": "A hash containing some bar resources.",
  "x-dynamic-key": {
    "document-id": "AcmeNamespaceBarBundle:Bar",
    "repository-method": "findAll",
    "ref-field": "id"
  }
}

The above example uses the id field of the Bar document as key in hashes. It uses the ref-field value of the resources found by findAll on AcmeNamespaceBarBundle:Bar as key and only allows those values as key.

Immutable fields in writable services

Graviton also certain fields to be made readonly. This may be used to add additional fields to a service that may not be changed by a client. This can be used to decorate services with additional info generated on the server side. One example of such a service is the /file endpoint where some of the metadata is generated on the server and may not be overridden by the client (ie. metadata.size).

{
  "name": "bar",
  "type": "string",
  "title": "Bar",
  "description": "A horse walks into a bar...",
  "readOnly": true

Relations

{
  "target": {
    "relations": [
      // ...
    ]
  }
}

Our schema allows you to specify relations between documents. Usually these relationships are added as type class:... field - the relations array of object allows you to specify what type of relation you want to generate if you want to override default behavior.

Info

If you don’t specify a relations entry for a given field, Graviton will generate a reference type of relation.

Having said that, it is usually good to still specify the exact kind of relationship you wish to create.

In order to force the generation of an embed relationship, you can notate:

{
  "target": {
    "relations": [
      {
        "type": "embed",
        "localValueField": "myfield"
      }
    ]
  }
}

You may decide which relation type you want to have depending on your use case as they have different circumstances:

A reference relationship:

  • Will save considerable amount of storage space in the database.
  • Will not allow the user to issue RQL queries against fields in referenced objects.

An embed relationship

  • Will duplicate the record every time the user saves it, resulting in more storage needs and leaving possibly abandoned copies of a document around.
  • Will allow the user to issue RQL queries against fields in embedded objects.

To the end user, the rendered output will be identical. This property only influences the internal storage of your structures.

Warning

Note that you may not change relation types of existing services! This will abandon all previously saved objects that are saved using the old structure. There is no automatic migration in place. If you need migrations please refer to the Migration guide.