An Example Object Model Style Guide (our actual style guide)

In this post we provide an example style guide (the one we use at DevRev).

An Example Object Model Style Guide (our actual style guide)

This is an example of our internal style guide when authoring new objects; while it may not be applicable, use this as an example and basis to build your own! If you didn’t get a chance to look at the object modeling best practices, I would recommend looking HERE.

Use snake_case

Attribute and type names must use snakecase instead of camel case or others. The ‘’ should be used at word boundaries replacing the space.

Do

my_cool_var
my_cool_type

Don’t

myCoolVar
mycoolvar

Use lowercase for object and attribute names

Object and attribute names must all be lowercase. Descriptions can contain capitalization.

Do

my_cool_var
my_cool_type

Don’t

myCoolVar
My_cool_var
My_Cool_Var

Postfix some types

For ID and Date/Timestamp types postfix the attribute name with the type.

Do

my_id       # id type
some_date   # date type

Don’t

my          # id type
some        # date type
foo_someid  # id type

NOTE: the _id should be lowercase in the attribute name but uppercase (ID) when referenced in the description.

Use required fields correctly

A required field must be specified on each request or generated by the system on create (is_system: true). For non-system fields, this means the caller must specify the field in every request.

If a field is necessary for a majority of objects, but not all, do not mark as required.

For example, if we require all objects have dev_oid, id, and some may have attr foo set:

Do

dev_oid
  ...
  is_required: true
  ...
id
  ...
  is_required: true
  ...
foo
  ...  # NOTE: is_required defaults to false

Don’t

dev_oid
  ...
  is_required: true
  ...
id
  ...
  is_required: true
  ...
foo
  ...
  is_required: true   # NOTE: is_required incorrectly set to true
  ...

NOTE: avoid accepting null as a valid value. If something may be null, the values should be re-considered.

Inherit from atom or a type that does

All types in the system must inherit from atom directly or indirectly (they inherit from a type who’s parent (directly or indirectly) inherits from atom). This ensures that every object in the system has the bare minimum required fields for things like multi-tenancy, identification, etc.

Do

- name: work
  parent: urn:devrev:objects:atom
  fields:
  ...
- name: issue
  parent: urn:devrev:objects:work
  fields:
  ...

Don’t

- name: not_work
  # NOTE: parent omitted
  fields:
  ...
- name: not_work_child
  parent: urn:devrev:objects:not_work
  ...

Set the devrev_field_type

The devrev_field_type must be set; please view the schema JSON to see a valid list of field types.

Set the devrev_id_type if the devrev_field_type is ID

When the devrev_field_type is set to id, the devrev_id_type must be specified to tell which type of id is expected. This is important to ensure only the correct types of IDs are stored.

Do

- name: atom
  fields:
    - name: created_by_id
      devrev_field_type: id
      devrev_id_type:
        - user
---
- name: dev_oid
  devrev_field_type: id
  devrev_id_type:
    - dev_org

Don’t

- name: not_atom
  fields:
    - name: my_id
      devrev_field_type: id
      # NOTE: devrev_id_type omitted

Must use a DON (not display ID) for any ID values

In the previous item, we dictate that you must set the devrev_id_type when the devrev_field_type is set to id. In coordination, the values of these MUST be in the DON format (don:...) and not the display ID format (TKT-111). This is imperative as the display ID doesn’t contain details about the dev oid as well as other useful information the DON provides (e.g., the region which will be critical in multi-region scenarios.)

Do

{
  "dev_oid": "don:...:dev_org/1"
  ...
}

Don’t

{
  "dev_oid": "DEVO-1"
  ...
}

NOTE: When referencing an ID in the description, ID must be capitalized (not id or Id)

Set is_system to true for any system-generated fields

For any system set field, ensure that is_system: true.

Do

- name: foo
  fields:
    - name: created_by_id
      devrev_field_type: id
      devrev_id_type:
        - user
      is_required: true
      is_system: true
    ...
    - name: bar
      devrev_field_type: tokens
      is_system: true
      # NOTE: is_required isn't always necessary for system fields

Don’t

- name: foo
  fields:
    - name: system_set_bar
      devrev_field_type: tokens
      # NOTE: is_system is not set to true
      # NOTE: value is actually set by the system
    ...

Provide a short, but full description

Descriptions should be succinct but also demonstrate the purpose of the attribute

Sort enum values logically and preference overridable_enum

When defining ENUMs, define the values logically (may be alphabetical in some instances and may not). Legacy_enums cannot be changed, so use them sparingly as they cannot be customized.

Do

- name: status
  fields:
    # NOTE: status would be a combination of the state and stage.  For example
    # active: pr approval pending, etc.
    - name: state
      devrev_field_type: overridable_enum # NOTE: overidable_enum used instead of legacy_enum
      devrev_enum: # NOTE: order is not alphabetical
        - open
        - in-progress
        - closed

Don’t

- name: status
  fields:
    # NOTE: status would be a combination of the state and stage.  For example
    # active: pr approval pending, etc.
    - name: state
      devrev_field_type: legacy_enum # NOTE: legacy_enum used instead of overridable_enum
      devrev_enum: # NOTE: order is not alphabetical or logical
        - open
        - closed
        - in-progress

NOTE: there may be some cases for using legacy_enum, however, this should be an uncommon case.

Set gateway summary field to mark items that should be in summary resolution

Gateway will commonly resolve IDs into their summary objects before returning the response. This process takes an ID and fetches details about the referenced ID. This is necessary as in most cases the ID isn’t too useful and the details about the aforementioned ID may be more useful.

For example, in work was have the applies_to_part_id attribute which references the part ID the work is associated with. However, the ID of the part isn’t too helpful as I want to know details about the part (e.g., name, owner, description, etc.).

In general, the attributes included in the summary should be the minimal set of attributes required to render a view without the need for subsequent requests.

Do Set important fields to summary: true

- name: title
  devrev_field_type: text
  is_required: true
  description: Title of the work object
  gateway:
    api_visibility: public
    api_required: true
    summary: true # NOTE: `title` included in summary
---
- name: severity
  devrev_field_type: legacy_enum
  devrev_enum:
    - blocker
    - high
    - medium
    - low
  is_required: true
  description: Severity of the ticket
  gateway:
    api_visibility: public
    is_filterable: true
    summary: true # NOTE: `severity` included in summary

Don’t Don’t set all fields to summary: true

      - name: field1
        ...
        gateway:
          api_visibility: public
          is_filterable: true
          summary: true                         # NOTE: all fields marked as `summary: true`
...
      - name: field2
        ...
        gateway:
          api_visibility: public
          is_filterable: true
          summary: true                         # NOTE: all fields marked as `summary: true`
...
      - name: field3
        ...
        gateway:
          api_visibility: public
          is_filterable: true
          summary: true                         # NOTE: all fields marked as `summary: true`

Override gateway id_resolution only when necessary

The id_resolution selector sets the resolution gateway can do for IDs. Valid values are none,summary (default),full.

  • Use none when you don’t any additional details about the referenced ID (just the ID will be sent in the response)
  • Use summary when summary details will do (defined via the summary gateway field). This is the default and should be good in most cases.
  • Use full only when you need the full referenced object’s details (NOTE: use sparingly as this may bloat the response size)

Do

...
- name: event_activity
    fields:
      - name: source_id
        devrev_field_type: id
        devrev_id_type:
          - change_event
        description: The ID correspending to the event
        is_required: true
        gateway:
          description: Timeline's event entry
          id_resolution: full                 # NOTE: id_resolution correctly overridden to full as the full event details are necessary
          name: change_event
...

Do

- name: link_id
  devrev_field_type: id
  devrev_id_type:
    - link
  is_required: true
  is_system: true
  gateway:
    id_resolution: none # NOTE: id_resolution correctly overridden to none as the link_id details are not necessary

Don’t

---
- name: applies_to_part_id
  devrev_field_type: id
  devrev_id_type:
    - part
  is_required: true
  description: Details of the part relevant to the work
  gateway:
    is_filterable: true
    id_resolution: full # NOTE: this would cause a signifigant of bloat on this object's response and full should not be used in this case given the size of the part object.
    name: applies_to_part

Set gateway name for any postfixed attribute names where necessary

Do

- name: marketplace_id
    devrev_field_type: id
    devrev_id_type:
      - marketplace
    description: The marketplace the item belongs to
    gateway:
      name: marketplace                         # NOTE: `marketplace_id` correctly overridden to `marketplace`

Don’t

- name: marketplace_id
    devrev_field_type: id
    devrev_id_type:
      - marketplace
    description: The marketplace the item belongs to
    gateway:
      description: The marketplace the item belongs to  # NOTE: redundant, only override when necessary
      # NOTE: name not overridden, will appear as `marketplace_id`

Set gateway api_visibility

Sets the api visibility for the field. Valid values are private, public, hidden.

Do

  • Use private when things should only be internally available via the internal API endpoints
  • Use hidden when things should be omitted from API generation and does not create an API field for the attribute. Use for deprecated fields
  • Use public when the attribute should be visible in the public API

Don’t

  • Don’t use private for things that should be public and are required by the app/client
  • Don’t use public for fields that should only be accessible internally

When to use composite types and mixins

Use mixins when:

  • need a easily reusable common set of attributes that may be used by various types (e.g., MFZ mixin, links mixin, etc.)

Use composite types when:

  • need an attribute to contain a nested structure instead of a simple value

NOTE: When you use a mixin the attributes of the mixin are “mixed in” to the objects attributes and do not need to be defined. when a composite type is added to a type the attribute needs to be added with a reference to the composite type.

How to define a composite type

devrev_composite_field_types:
  - name: my_composite_type
    fields:
      - name: key
        devrev_field_type: tokens
        is_required: true
        description: Key used to fetch the object value
        ui:
          display_name: Key
      - name: vals
        devrev_field_type: "[]tokens"
        is_required: true
        description: Values valid for the key
        ui:
          display_name: Value(s)
    description: My composite type

How to reference a composite type

- name: my_type
  parent: urn:devrev:objects:atom
  fields:
    - name: my_cool_composite_type # NOTE: name doesn't need to math the composite type name
      devrev_field_type: "[]composite"
      is_required: true
      description: Example addition of a composite type
      ui:
        display_name: My composite type
      devrev_composite_type: my_composite_type

How to define a mixin

NOTE: define mixins in separate files.

mixin_types:
---
- name: my_status_mixin
  fields:
    # NOTE: status would be a combination of the state and stage.  For example
    # active: pr approval pending, etc.
    - name: state
      devrev_field_type: overridable_enum
      devrev_enum:
        - open
        - in-progress
        - closed
      is_required: true
      is_system: true
      description: State of the object based upon the stage
      gateway:
        is_filterable: true
      ui:
        display_name: State
        is_hidden: true
    - name: stage
      devrev_field_type: composite
      description: Stage of the object
      is_required: true
      gateway:
        api_visibility: public
        is_filterable: true
      ui:
        display_name: Stage
        is_bulk_action_enabled: true
        is_shown_in_summary: true
        is_hidden_during_create: true
      devrev_composite_type: stage
  description: Mixin for status attributes

How to use a mixin

object_schemas:
  - name: work
    parent: urn:devrev:objects:atom
    mixins:
      - name: status
        fields_prefix: ""
    ...
  - name: another_example
    parent: urn:devrev:objects:atom
    mixins:
      - name: status
        fields_prefix: ""

Example:

{
  "bar":1,
  "bas":"aaa",
  "boo":          # NOTE: example of a top level composite type, may be via a mixin
    {
      "x":1,
      "y":3,
      "z":        # NOTE: example of a nested composite type, within the "boo" composite type
        [         # NOTE: nested types can be a known type, structure or [] of structures
          {
          "a":1,
          "b":2
          },
          {
          "a":2,
          "b":3
          },
        ]
    }
}

REMINDER: composite types may be nested.

For example, in MFZ a group has an attribute for conditional_roles which is a composite type that includes an array of conditions which is another composite type. The only way to model this nesting is with composite types.

Do

- name: group
  parent: urn:devrev:objects:atom
  fields:
    - name: conditional_roles
      devrev_field_type: "[]composite"
      is_required: true
      description: Conditional roles for the group
      ui:
        display_name: Conditional roles
      devrev_composite_type: conditional_role
---
devrev_composite_field_types:
  - name: conditional_role
    fields:
      - name: conditions
        devrev_field_type: "[]composite"
        is_required: true
        description: Conditions definition which must be met for role
        ui:
          display_name: Conditions
        devrev_composite_type: condition
---
- name: condition
  fields:
    - name: key
      devrev_field_type: tokens
      is_required: true
      description: Key used to fetch the object value
      ui:
        display_name: Key
    - name: vals
      devrev_field_type: "[]tokens"
      is_required: true
      description: Values valid for the key
      ui:
        display_name: Value(s)
    - name: operator
      devrev_field_type: legacy_enum
      devrev_enum:
        - eq
        - in
        - not_eq
        - not_in
      description: The operator used to compare the object vals
      ui:
        display_name: Operator
  description: Condition

Don’t

# NOTE: It isn't possible to structure nested types without composite types

Do

Use a mixin to add a common set of attributes across object types. If the field_prefix is set to "" the attributes will be added as defined in the mixin. If field_prefix is set to something other than "", for example, "foo_" the mixins attributes will be added as foo_[attr_name].

NOTE: to keep consistent with the snake*case requirement, the field_prefix must end with a *to give the structure prefix\_[attr_name]

object_schemas:
  - name: work
    parent: urn:devrev:objects:atom
    mixins:
      - name: link
        fields_prefix: ""
      - name: reaction
        fields_prefix: ""
      - name: status
        fields_prefix: ""
    ...
  - name: another_example
    parent: urn:devrev:objects:atom
    mixins:
      - name: status
        fields_prefix: ""
mixin_types:
...
  - name: status
    fields:
      # NOTE: status would be a combination of the state and stage.  For example
      # active: pr approval pending, etc.
      - name: state
        devrev_field_type: overridable_enum
        devrev_enum:
          - open
          - in-progress
          - closed
        is_required: true
        is_system: true
        description: State of the object based upon the stage
        gateway:
          is_filterable: true
        ui:
          display_name: State
          is_hidden: true
      - name: stage
        devrev_field_type: composite
        description: Stage of the object
        is_required: true
        gateway:
          api_visibility: public
          is_filterable: true
        ui:
          display_name: Stage
          is_bulk_action_enabled: true
          is_shown_in_summary: true
          is_hidden_during_create: true
        devrev_composite_type: stage
    description: Mixin for status attributes

Don’t

Duplicate a common set of attributes by defining in each type. Notice how the state and stage attributes are defined in each type instead of a) using a composite type and b) using a mixin.

object_schemas:
  - name: work
    parent: urn:devrev:objects:atom
    fields:
      # NOTE: status would be a combination of the state and stage.  For example
      # active: pr approval pending, etc.
      - name: state
        devrev_field_type: overridable_enum
        devrev_enum:
          - open
          - in-progress
          - closed
        is_required: true
        is_system: true
        description: State of the object based upon the stage
        gateway:
          is_filterable: true
        ui:
          display_name: State
          is_hidden: true
      - name: stage
        devrev_field_type: composite
        description: Stage of the object
        is_required: true
        gateway:
          api_visibility: public
          is_filterable: true
        ui:
          display_name: Stage
          is_bulk_action_enabled: true
          is_shown_in_summary: true
          is_hidden_during_create: true
        devrev_composite_type: stage
    ...
  - name: another_example
    parent: urn:devrev:objects:atom
    fields:
      # NOTE: status would be a combination of the state and stage.  For example
      # active: pr approval pending, etc.
      - name: state
        devrev_field_type: overridable_enum
        devrev_enum:
          - open
          - in-progress
          - closed
        is_required: true
        is_system: true
        description: State of the object based upon the stage
        gateway:
          is_filterable: true
        ui:
          display_name: State
          is_hidden: true
      - name: stage
        devrev_field_type: composite
        description: Stage of the object
        is_required: true
        gateway:
          api_visibility: public
          is_filterable: true
        ui:
          display_name: Stage
          is_bulk_action_enabled: true
          is_shown_in_summary: true
          is_hidden_during_create: true
        devrev_composite_type: stage
...
essential