API Generation

April 10, 2025 ยท View on GitHub

Features

Resource Ownership

Global

The entity is owned by the system.

All operations that work on a global entity do not need to be restricted implicitly.

Tenant

The entity is owned by the tenent.

All operations that work on a tenant entity be restricted implicitly by the current tenantId (i.e. include a tenantId = {tenantId} in the WHERE clause)

User

The entity is owned by the user.

All operations that work on a user entity be restricted implicitly by the current userId (i.e. include a tenantId = {tenantId} AND userId = {userId} in the WHERE clause)

Entity Schema

Entity Keywords

KeywordDescriptionSchemaDefault
ownershipThe subject that owns the resourceenum: [ "global", "tenant", "user" ]global
singletonFlags the entity as a singleton under the resource ownerbooleanfalse
aliasReplaces the entity name in all OpenAPI terms (path, params, etc)string
versionedFlags the entity as versioned (stores revision history)booleanfalse
readOnlyFlags the entity as readOnly in the APIbooleanfalse
createableFlags the entity as createable in the API (can be created)booleantrue
editableFlags the entity as editable in the API (can be updated after create)booleantrue
deleteableFlags the entity as deleteable in the API (can be deleted after create)booleantrue

Property Keywords

KeywordDescriptionSchema
readOnlyFlags the field as readOnly in the APIboolean
data-classificationDefines the data classificationenum: [ "public", "sensitive ]

Example Driven Architecture

We want to use examples to help us mock out our APIs and to simplify API testing (we could use examples for known good inputs and outputs).

OpenAPI allows for multiple examples for parameters, requestBodies, and responses, where each example has a unique id stored as object properties

requestBody:
  content:
    application/json: # Media type
      schema: # Request body contents
        $ref: "#/components/schemas/User" # Reference to an object
      examples: # Child of media type
        Jessica: # Example 1
          value:
            id: 10
            name: Jessica Smith
        Ron: # Example 2
          value:
            id: 11
            name: Ron Stewart

The above has two examples defined for the requestBody, Jessica and Ron. We can use a convention

OpenAPI Backend is a tool we may wish to recommend for mocking purposes. It has the ability to mock out a server stub by returning a response based on the OpenAPI examples. When mocking the response you can optionally specificy the named example. It uses the following precedence:

  1. If a named example was given, and examples is defined, and the named example exists in the examples with a value then return the named example
  2. Otherwise, if a single example is given in the component, then use the singular example
  3. Otherwise, if the component has multiple examples, use the first named example
  4. Otherwise, mock the response from the JSON schema
  5. If the JSON schema has an example, then it will use the schema example .
  6. Otherwise, it will generate a value using the JSON schema and defined defaults.

Case Conventions

Models

Use caseCase ConventionExample
Entity namesPascalCaseRelationshipStatus
Property namescamelCaserelationshipStatus

Paths

Use caseCase ConventionExample
Static Segmentkebab-caserelationship-status
Dynamic ParameterscamelCase{relationshipStatusId}

Standard Operations

Queries

findOne

Used to retrieve a single resource from an optional parent collection.

Example

Find a specific physician and include the physician's favourite and relationship status.

findOne:
  - physician
filterBy:
  - physician
join:
  - favorite
  - relationship-status
findAll

Used to list all of child resources from a parent collection.

Example

List all physicians in a given region and include the physician's favourite and relationship status.

findAll:
  - territory
  - physician
filterBy:
  - territory
join:
  - favorite
  - relationship-status

Used to search for a specific type of resource.

Example

Search for a physician:

search: physician
query

Used to create a named-query that runs a complex SQL query that produces dimensions (entity properties) and measures (aggregations).

KeywordDescriptionSchemaDefault
nameThe name of the querystring
descriptionA human readable description of the querystring
queryThe query objectQuery
Query Schema
KeywordDescriptionSchemaDefault
filterableByThe name of the querystring
sortableByA human readable description of the querystring
dimensionsThe query objectQuery
measuresThe query objectQuery

Example

Calculate the total number of widgets, grouped by widget (id and name), with an optional filter on the widget id:

name: total-widgets
description: Calculates the total number of widgets, by type
query:
  filterableBy:
    - widget: ["id"]
  dimensions:
    - projection:
        widget: ["id", "name"]
  measures:
    - name: total
      type: integer

Mutations

The system will automatically generate paths based on the entity and relationships. There are two types of paths generated for all mutations, the container path and the item path.

PathExample
Container/stores/{storeId}/pets
Item/stores/{storeId}/pets/{petId}
CRUD

The CRUD operations are used whenever an entity as a one-to-many relationship with a parent entity that is a composition relationship

The standard conventions are:

OperationPathReturn Code
createContainer201 Created
updateItem200 OK
deleteContainer204 No Content
Natural Identifiers

Schema authors should not include the "id" property for any schemas with system managed identifiers, the code generator will include these on your behalf.

Some schemas may have "natural identifiers". That is to say, identifiers that are well known, are contextual, and are expected to be known by the caller when entities are created. In this scenario, the schema authors may define the natural identifiers via the "id" property in the entity schema. For example:

type: object
properties:
  id:
    type: string
    enum: ["dog", "cat", "bunny"]

When the natural identifier is specified the id schema will be used as the entity Identifier schema. Moreover, the CREATE operation for the entity will use the item path, rather than the container path, as it is expected that the caller will include the id in the create operation. For example:

POST /stores/{storeId}/pets/bunny
Upsert

The Upsert operations are used whenever an entity as a one-to-one relationship with a parent entity that is a composition relationship

OperationPathReturn Code
upsertItem200 OK

Service Specification Notes

Queries

  • We should figure out what we need in the generator, then figure out the minimal amount we need in the YAML to make it work.
  • Not all entities should have a default list routes
    • Entities that are expected to be navigated to via an aggregation relationship
      • Physicians are normally found via territory/region dashboard or search
      • Favorites, Relationship statuses, etc. are normally read through joins on physician
  • Not all entities may require a retrieve route
    • The territory and/or regions may only need to be accessed via lists in the end-user service (retrieve will be necessary in the admin service)
  • How do we handle hierarchical relationhips
    • State 1.._ > City 1.._ > Hospital
    • State 1..* > Hospital
  • How do we handle Auth[N|Z]
    • For now, we assume no operations allow anonynous access...

Relationships

  • Default the relationship id to be the right entity name (pluralized if it is toMany)

Entity Identifiers

  • By default, we probably want to use UUID or Integer ids

ReadOnly properties

  • Delete them from the input entity