Skip to content
Anthony Truskinger edited this page Aug 27, 2024 · 5 revisions

Specialized actions:

  • Should be avoided as RPC is a REST anti-pattern
  • Be avoided when there are significantly different result payloads returned. This may indicate a design better represented as a nested resource

Actions are good choice for manipulating a state machine. They should also significantly improve our OpenAPI documentation.

If absolutely needed an action should be exposed:

  • SHOULD take the form of POST /resource/{verb}
    • it is nested under a resource (e.g. /analysis_jobs/1/suspend)
    • it is nested a under a single resource, not a list or filter endpoint
      • e.g. NOT /analysis_jobs/
  • MUST NOT be invokable with a GET request - the GET verb is idempotent, it should not affect state
  • SHOULD be invoked with a POST verb
    • however, PUT is acceptable but unlikely to be relevant
      • PUT indicates the replacement of a state and should be idempotent
      • It is unlikely you need an action (that has a side effect) which is also idempotent
    • however, DELETE is also acceptable if the semantics are correct
  • The invocation MUST have a side effect
  • May accept a body with parameters
    • But if a lot of parameters are needed then this is a code smell: perhaps you should reconsider the design of your resource?
    • We don't currently have any example of actions accepting parameters. If we find such cases, we need to reconsider this guidance.
  • May return a body with information about the action
    • We don't currently have any example of actions returning a body. If we find such cases, we need to reconsider this guidance.
  • Should return 204 No Content
  • We set a Location header for a successful response but this is technically not in spec.
  • The resource should return cache headers that reflect that caching is not allowed
  • It must NOT return the resource
    • e.g. analysis_jobs/1/suspend should not return /analysis_jobs/1
    • We have a GET request on the resource itself for that

Examples

Invoke an action

POST /analysis_jobs/1/retry
204 No Content
Location: /analysis_jobs/1
Cache-Control: no-cache


An invalid action response

POST /analysis_jobs/2/complete
404 Not Found
X-Error-Type: Custom Errors/Invalid Action Error
Content-Type: application/json; charset=utf-8
Cache-Control: no-cache

{
    "meta": {
        "status": 404,
        "message": "Not Found",
        "error": {
            "details": "Invalid action: unknown action: `complete`",
            "links": {
                "retry": "/analysis_jobs/2/retry",
                "resume": "/analysis_jobs/2/resume",
                "suspend": "/analysis_jobs/2/suspend",
                "amend": "/analysis_jobs/2/amend"
            },
            "info": {
                "allowed_actions": [
                    "retry",
                    "resume",
                    "suspend",
                    "amend"
                ]
            }
        }
    },
    "data": null
}

Historical notes

We have various models which are modeled as state machines.

We would previously allow state transitions by PATCHing the state-machine's backing field. e.g. PATCH harvests/123 with a body of {"harvest:{"status":"scanning"}}. This worked but it introduces a few problems:

  • given a desired state, we had to infer which event (transition) method to call
  • this requires flat state machines - i.e. only one transition (path) possible between states
    • which in turn, requires more states in the machine, which exponentially increases the number of possible cases.
  • it also makes it awkward to affect more than one state machine on a resource
  • and the naming is awkward
    • rather than execute a verb that shows intent (e.g. POST harvests/123/scan)
    • we sent an adjective like scanning

Most of our state machines still follow the PATCH strategy. This behaviour should be considered deprecated and URL actions as described in this document should be implemented at an opportune time.