diff --git a/ipa/general/0101.md b/ipa/general/0101.md deleted file mode 100644 index 15e19ba..0000000 --- a/ipa/general/0101.md +++ /dev/null @@ -1,103 +0,0 @@ ---- -id: 101 -state: adopt ---- - -# IPA-101: Resource-Oriented Design - -Resource-oriented design is a pattern for specifying APIs based on several -high-level design principles: - -- The fundamental building blocks of an API are - [individually named resources](0102.md) (nouns) and the relationships and - hierarchy that exist between them -- A small number of [standard methods](0103.md) (verbs) provide the semantics - for most common operations - - [Custom methods](0109.md) are available in situations where the standard - methods do not fit. -- Stateless protocol: Each interaction between the client and the server are - independent, and both the client and server have clear roles - -## Guidance - -When designing an API, consider the following (roughly in logical order): - -- The resources (nouns) the API will provide -- The relationships and hierarchies between those resources -- The schema of each resource -- The methods (verbs) each resource provides rely as much as possible on the - [standard verbs](0103.md) - -### Resources - -- A resource-oriented API **should** generally be modeled as a resource - hierarchy - - Each node is either a simple resource or a collection of resources - - A collection contains resources of the same type - - A resource usually has fields -- Resources **may** have any number of sub-resources -- The schema for a resource **must** be the same across all methods related to - the resource - -:::warning[Important] - -API producers **should not** expect that their API will be reflective of their -database schema. Having an API that is identical to the underlying database -schema is an antipattern, as it tightly couples the surface to the underlying -system - -::: - -### Methods - -A typical resource-oriented API exposes a large number of resources with a small -number of methods on each resource and **should not** be confused with the HTTP -methods. The methods here described related to the operations available on a -resource. - -The methods can be: - -- The standard methods ([Get](0104.md), [List](0105.md), [Create](0106.md), - [Update](0107.md), [Delete](0108.md)) -- [Custom methods](0109.md) - -The following table illustrates the relationship between resources and the -standard methods: - -| Standard method | Request | Response | -| --------------- | ---------------------------------------------- | ----------------------- | -| Create | Contains the future resource | Is the resource | -| Get | None | Is the resource | -| Update | Contains the resource or parts of the resource | Is the current resource | -| Delete | None | None | -| List | None | Are the resources | - -- A resource **must** support at minimum [Get](0104.md) - - Clients **must** be able to validate the state of resources after performing - a mutation such as [Create](0106.md), [Update](0107.md), or - [Delete](0108.md) -- A resource **must** also support [List](0105.md) except for - [singleton resources](0113.md) where more than one resource is not possible -- APIs **should** prefer standard methods over custom methods - - [Custom methods](0109.md) help define functionality that does not cleanly - map to any of the standard methods - -### Read-Only Resources - -Read-only resources are resources that cannot be modified by API consumers. - -- Read-only resources **must** have [Get](0104.md) and [List](0105.md) methods -- Read-only resources **must not** have [Create](0106.md), [Update](0107.md), or - [Delete](0108.md) methods -- Read-only resources **may** have [custom methods](0109.md) as appropriate -- All response schema properties for read-only resources **must** be marked as - read-only - - In OpenAPI, this means all properties **must** have `readOnly: true` - - All fields in read-only resources are server-owned. For guidance on - server-owned fields, see [IPA-111](0111.md#single-owner-fields) -- Unsupported operations on read-only resources **should** return - `405 Not Allowed` -- Unsupported operations **must not** be documented - - Some declarative-friendly clients require all standard methods to be - implemented, but we consider documented unsupported methods to be in - detriment of generated documentation and code diff --git a/ipa/general/0101.mdx b/ipa/general/0101.mdx new file mode 100644 index 0000000..32dd9c2 --- /dev/null +++ b/ipa/general/0101.mdx @@ -0,0 +1,948 @@ +--- +id: 101 +state: adopt +--- + +# IPA-101: Resource-Oriented Design + +Resource-oriented design is a pattern for specifying APIs based on several +high-level design principles: + +- The fundamental building blocks of an API are + [individually named resources](0102.md) (nouns) and the relationships and + hierarchy that exist between them +- A small number of [standard methods](0103.md) (verbs) provide the semantics + for most common operations + - [Custom methods](0109.md) are available in situations where the standard + methods do not fit. +- Stateless protocol: Each interaction between the client and the server are + independent, and both the client and server have clear roles + +## Guidance + +When designing an API, consider the following (roughly in logical order): + +- The resources (nouns) the API will provide +- The relationships and hierarchies between those resources +- The schema of each resource +- The methods (verbs) each resource provides rely as much as possible on the + [standard verbs](0103.md) + +### Resources + +- A resource-oriented API **should** generally be modeled as a resource + hierarchy + - Each node is either a simple resource or a collection of resources + - A collection contains resources of the same type + - A resource usually has fields +- Resources **may** have any number of sub-resources +- The schema for a resource **must** be the same across all methods related to + the resource + +:::warning[Important] + +API producers **should not** expect that their API will be reflective of their +database schema. Having an API that is identical to the underlying database +schema is an antipattern, as it tightly couples the surface to the underlying +system + +::: + +### Methods + +A typical resource-oriented API exposes a large number of resources with a small +number of methods on each resource and **should not** be confused with the HTTP +methods. The methods here described related to the operations available on a +resource. + +The methods can be: + +- The standard methods ([Get](0104.md), [List](0105.md), [Create](0106.md), + [Update](0107.md), [Delete](0108.md)) +- [Custom methods](0109.md) + +The following table illustrates the relationship between resources and the +standard methods: + +| Standard method | Request | Response | +| --------------- | ---------------------------------------------- | ----------------------- | +| Create | Contains the future resource | Is the resource | +| Get | None | Is the resource | +| Update | Contains the resource or parts of the resource | Is the current resource | +| Delete | None | None | +| List | None | Are the resources | + +- A resource **must** support at minimum [Get](0104.md) + - Clients **must** be able to validate the state of resources after performing + a mutation such as [Create](0106.md), [Update](0107.md), or + [Delete](0108.md) +- A resource **must** also support [List](0105.md) except for + [singleton resources](0113.md) where more than one resource is not possible +- APIs **should** prefer standard methods over custom methods + - [Custom methods](0109.md) help define functionality that does not cleanly + map to any of the standard methods + +### Read-Only Resources + +Read-only resources are resources that cannot be modified by API consumers. + +- Read-only resources **must** have [Get](0104.md) and [List](0105.md) methods +- Read-only resources **must not** have [Create](0106.md), [Update](0107.md), or + [Delete](0108.md) methods +- Read-only resources **may** have [custom methods](0109.md) as appropriate +- All response schema properties for read-only resources **must** be marked as + read-only + - In OpenAPI, this means all properties **must** have `readOnly: true` + - All fields in read-only resources are server-owned. For guidance on + server-owned fields, see [IPA-111](0111.md#single-owner-fields) +- Unsupported operations on read-only resources **should** return + `405 Not Allowed` +- Unsupported operations **must not** be documented + - Some declarative-friendly clients require all standard methods to be + implemented, but we consider documented unsupported methods to be in + detriment of generated documentation and code + + + + + +A resource-oriented API **should** generally be modeled as a resource hierarchy, +where each node is either a simple resource or a collection of resources of the +same type. + + + +```yaml +paths: + /api/atlas/v2/groups/{groupId}/clusters: + get: + operationId: listClusters + /api/atlas/v2/groups/{groupId}/clusters/{clusterName}: + get: + operationId: getCluster + /api/atlas/v2/groups/{groupId}/clusters/{clusterName}/backup/snapshots: + get: + operationId: listSnapshots +``` + + + Paths form a clear hierarchy: a `clusters` collection holds individual + `cluster` resources, each of which owns a `snapshots` sub-collection. The + nesting reflects the real ownership relationships between resources. + + + + + + +```yaml +paths: + /api/atlas/v2/getClusterBackupData: + get: + operationId: getClusterBackupData + /api/atlas/v2/doSnapshotLookup: + get: + operationId: doSnapshotLookup +``` + + + The paths are flat, verb-shaped RPC endpoints with no resource hierarchy. + Clients cannot infer ownership or navigate between related resources because + the structure encodes actions rather than nouns. + + + + + + + Enumerate all paths in the specification and group them by their resource + segments. + + + Confirm that each path alternates between collection nouns and resource + identifiers (e.g., `/clusters/{clusterName}/snapshots/{snapshotId}`) rather + than encoding verbs or actions in the path. + + + Verify that nested paths reflect genuine ownership or containment + relationships between the parent and child resources. + + + Confirm each collection contains resources of a single type. + + + Report any paths that are flat, verb-shaped, or that do not fit into a + coherent resource hierarchy. + + + + + + + +Resources **may** have any number of sub-resources. + + + + + +The schema for a resource **must** be the same across all methods related to the +resource. + + + +```yaml +components: + schemas: + Cluster: + type: object + properties: + id: { type: string } + name: { type: string } + regionName: { type: string } +paths: + /api/atlas/v2/groups/{groupId}/clusters/{clusterName}: + get: + responses: + "200": + content: + application/json: + schema: + $ref: "#/components/schemas/Cluster" + put: + responses: + "200": + content: + application/json: + schema: + $ref: "#/components/schemas/Cluster" +``` + + + Both the Get and Update methods return the same `Cluster` schema, so clients + see one stable representation of the resource regardless of which method they + call. + + + + + + +```yaml +paths: + /api/atlas/v2/groups/{groupId}/clusters/{clusterName}: + get: + responses: + "200": + content: + application/json: + schema: + type: object + properties: + id: { type: string } + name: { type: string } + regionName: { type: string } + put: + responses: + "200": + content: + application/json: + schema: + type: object + properties: + clusterId: { type: string } + clusterName: { type: string } +``` + + + The Get and Update methods return divergent representations of the same + cluster (different field names, missing fields). Clients cannot rely on a + single consistent shape for the resource across operations. + + + + + + + Identify the resource under review and collect every operation that returns + or accepts that resource (Get, List, Create, Update). + + + Resolve the request and response schemas referenced by each of those + operations. + + + Compare the property names, types, and structure across all of those + schemas. + + + Flag any operation whose schema introduces, renames, or omits fields + relative to the canonical resource representation. + + + + + + + +API producers **should not** expect that their API will be reflective of their +database schema, as having an API identical to the underlying database tightly +couples the surface to the underlying system. + + + +```yaml +components: + schemas: + Cluster: + type: object + properties: + id: { type: string } + name: { type: string } + regionName: { type: string } +``` + + + The resource exposes a stable API surface independent of storage internals. + Clients are decoupled from the underlying data model. + + + + + + +```yaml +components: + schemas: + Cluster: + type: object + properties: + _id: { type: string } + __v: { type: integer } + ns: { type: string } +``` + + + Exposing `_id`, `__v`, and `ns` leaks MongoDB document internals into the API + surface, coupling clients to the storage layer and making the schema brittle + to database refactors. + + + + + + + Locate the technical design document or data model for the API under review. + + + Compare resource names in the API against entity or table names in the data + model. + + + Compare field names in the API response schemas against column or document + field names in the data model. + + + Check whether the API resource hierarchy mirrors the database relationship + model one-to-one. + + + If no design doc is available, inspect the schemas for storage-layer + artifacts (e.g., `_id`, `__v`, raw collection names). + + + Report any tight coupling found, or confirm the API surface is independent + of the storage model. + + + + + + + +A resource **must** support at minimum the [Get](0104.md) method. + + + +```yaml +paths: + /api/atlas/v2/groups/{groupId}/clusters/{clusterName}: + get: + operationId: getCluster + responses: + "200": + description: OK +``` + + + The cluster resource exposes a Get method, so consumers can retrieve and + validate the state of an individual resource. + + + + + + +```yaml +paths: + /api/atlas/v2/groups/{groupId}/clusters/{clusterName}: + put: + operationId: updateCluster + responses: + "200": + description: OK + delete: + operationId: deleteCluster + responses: + "204": + description: No Content +``` + + + The resource supports mutation but offers no Get method, so consumers have no + way to read back the resource after creating or updating it. + + + + + + + Identify each individually named resource path in the specification (a path + ending in a resource identifier such as `/clusters/{clusterName}`). + + + For each such resource, check whether a `get` operation is defined. + + Report any resource that lacks a Get method. + + + + + + +Clients **must** be able to validate the state of resources after performing a +mutation such as [Create](0106.md), [Update](0107.md), or [Delete](0108.md). + + + + + +A resource **must** also support the [List](0105.md) method, except for +[singleton resources](0113.md) where more than one resource is not possible. + + + +```yaml +paths: + /api/atlas/v2/groups/{groupId}/clusters: + get: + operationId: listClusters + responses: + "200": + description: OK + /api/atlas/v2/groups/{groupId}/clusters/{clusterName}: + get: + operationId: getCluster + responses: + "200": + description: OK +``` + + + The collection path exposes a List method alongside the per-resource Get, so + consumers can both enumerate clusters and retrieve any individual one. + + + + + + +```yaml +paths: + /api/atlas/v2/groups/{groupId}/clusters/{clusterName}: + get: + operationId: getCluster + responses: + "200": + description: OK +``` + + + Only the individual cluster can be fetched; there is no collection-level List + method, so consumers cannot discover which clusters exist. The resource is not + a singleton, so List is required. + + + + + + + Identify each resource collection in the specification (a path ending in a + collection noun such as `/clusters`). + + + Determine whether the resource is a singleton (only one instance can ever + exist); if so, List is not required. + + + For every non-singleton resource, check whether a collection-level `get` + (List) operation is defined. + + + Report any non-singleton resource that lacks a List method. + + + + + + + +APIs **should** prefer standard methods over [custom methods](0109.md), using +custom methods only for functionality that does not cleanly map to a standard +method. + + + +```yaml +paths: + /api/atlas/v2/groups/{groupId}/clusters/{clusterName}: + put: + operationId: updateCluster + responses: + "200": + description: OK +``` + + + Updating a cluster maps cleanly to the standard Update method, so a standard + method is used instead of a custom one. + + + + + + +```yaml +paths: + /api/atlas/v2/groups/{groupId}/clusters/{clusterName}:setName: + post: + operationId: setClusterName + responses: + "200": + description: OK +``` + + + Renaming a cluster is just a field update and maps cleanly to the standard + Update method, so introducing a `:setName` custom method is unnecessary + surface area. + + + + + + + Identify every custom method in the specification (operations using the + `:customVerb` path suffix convention). + + + For each custom method, determine the operation it performs and whether it + maps to a standard method (Get, List, Create, Update, Delete). + + + Flag any custom method whose behavior could be expressed with a standard + method instead. + + + + + + + +Read-only resources **must** have [Get](0104.md) and [List](0105.md) methods. + + + +```yaml +paths: + /api/atlas/v2/groups/{groupId}/processes: + get: + operationId: listProcesses + responses: + "200": + description: OK + /api/atlas/v2/groups/{groupId}/processes/{processId}: + get: + operationId: getProcess + responses: + "200": + description: OK +``` + + + The read-only `processes` resource exposes both List and Get, so consumers can + enumerate processes and inspect any individual one even though they cannot + modify them. + + + + + + +```yaml +paths: + /api/atlas/v2/groups/{groupId}/processes/{processId}: + get: + operationId: getProcess + responses: + "200": + description: OK +``` + + + The read-only resource exposes Get but no collection-level List, so consumers + cannot discover which processes exist. + + + + + + + Identify resources that are read-only (consumers cannot create, update, or + delete them). + + + For each read-only resource, confirm a per-resource `get` (Get) operation is + defined. + + + Confirm a collection-level `get` (List) operation is also defined. + + + Report any read-only resource missing either the Get or the List method. + + + + + + + +Read-only resources **must not** have [Create](0106.md), [Update](0107.md), or +[Delete](0108.md) methods. + + + +```yaml +paths: + /api/atlas/v2/groups/{groupId}/processes: + get: + operationId: listProcesses + responses: + "200": + description: OK + /api/atlas/v2/groups/{groupId}/processes/{processId}: + get: + operationId: getProcess + responses: + "200": + description: OK +``` + + + The read-only process resource only exposes read operations (Get and List); no + Create, Update, or Delete methods are present. + + + + + + +```yaml +paths: + /api/atlas/v2/groups/{groupId}/processes/{processId}: + get: + operationId: getProcess + responses: + "200": + description: OK + delete: + operationId: deleteProcess + responses: + "204": + description: No Content +``` + + + A read-only resource must not be mutable; exposing a Delete method contradicts + its read-only contract and lets consumers modify server-owned state. + + + + + + + Identify resources that are read-only (consumers cannot modify them). + + + For each read-only resource, inspect its operations for any `post` (Create), + `put`/`patch` (Update), or `delete` (Delete) methods. + + + Report any mutation method present on a read-only resource. + + + + + + + +Read-only resources **may** have [custom methods](0109.md) as appropriate. + + + + + +All response schema properties for read-only resources **must** be marked as +read-only — in OpenAPI, every property **must** have `readOnly: true`. + + + +```yaml +components: + schemas: + Process: + type: object + properties: + id: + type: string + readOnly: true + hostname: + type: string + readOnly: true + port: + type: integer + readOnly: true +``` + + + Every property of the read-only resource is marked `readOnly: true`, so the + schema accurately communicates that all fields are server-owned and cannot be + set by consumers. + + + + + + +```yaml +components: + schemas: + Process: + type: object + properties: + id: + type: string + readOnly: true + hostname: + type: string + port: + type: integer +``` + + + `hostname` and `port` lack `readOnly: true`, so the schema implies consumers + can set them even though the resource is read-only and all its fields are + server-owned. + + + + + + + Identify resources that are read-only and locate their response schemas. + + + Enumerate every property defined in each of those response schemas, + including nested object properties. + + Confirm each property carries `readOnly: true`. + + Report any property of a read-only resource that is not marked `readOnly: + true`. + + + + + + + +Unsupported operations on read-only resources **should** return +`405 Not Allowed`. + + + +```yaml +paths: + /api/atlas/v2/groups/{groupId}/processes/{processId}: + delete: + operationId: deleteProcess + responses: + "405": + description: Method Not Allowed +``` + + + When a consumer attempts an unsupported mutation on a read-only resource, the + server returns `405 Not Allowed`, signaling clearly that the operation is not + permitted. + + + + + + +```yaml +paths: + /api/atlas/v2/groups/{groupId}/processes/{processId}: + delete: + operationId: deleteProcess + responses: + "500": + description: Internal Server Error +``` + + + Returning `500` for an operation that is simply not allowed misleads consumers + into treating an intentional restriction as a server fault. The unsupported + operation should return `405 Not Allowed`. + + + + + + + Identify read-only resources and any operations on them that are not + supported. + + + For each unsupported operation that is nonetheless reachable, inspect the + documented response codes. + + + Confirm the operation responds with `405 Not Allowed` rather than a generic + success or error code. + + + Report any unsupported operation that does not return `405 Not Allowed`. + + + + + + + +Unsupported operations **must not** be documented in the specification, because +documented unsupported methods degrade generated documentation and code. + + + +```yaml +paths: + /api/atlas/v2/groups/{groupId}/processes/{processId}: + get: + operationId: getProcess + responses: + "200": + description: OK +``` + + + Only the supported Get operation is documented; the unsupported mutation + methods are omitted entirely, keeping generated SDKs and docs free of + operations that cannot be used. + + + + + + +```yaml +paths: + /api/atlas/v2/groups/{groupId}/processes/{processId}: + get: + operationId: getProcess + responses: + "200": + description: OK + delete: + operationId: deleteProcess + description: This operation is not supported. + responses: + "405": + description: Method Not Allowed +``` + + + Documenting an unsupported Delete operation pollutes generated SDKs and + documentation with a method consumers can never use, even when it is annotated + as unsupported. + + + + + + + Scan the specification for operations described or annotated as unsupported, + not implemented, or method-not-allowed. + + + For each, determine whether the operation is documented purely to signal + that it is unavailable. + + + Report any unsupported operation that appears in the specification; it + should be removed rather than documented. + + + + + + diff --git a/ipa/general/0106.md b/ipa/general/0106.md index c619f20..6b373c1 100644 --- a/ipa/general/0106.md +++ b/ipa/general/0106.md @@ -13,7 +13,7 @@ collection. - APIs **should** provide a create method for resources unless it is not valuable for users to do so - - [Read-only resources](0101.md#read-only-resources) **must not** have a + - [Read-only resources](0101.mdx#read-only-resources) **must not** have a Create method - [Singleton resources](0113.md) **must not** have a Create method - The purpose of the create method is to create a new resource in a collection diff --git a/ipa/general/0107.md b/ipa/general/0107.md index 60121f5..b519eb6 100644 --- a/ipa/general/0107.md +++ b/ipa/general/0107.md @@ -13,7 +13,7 @@ resource. - APIs **should** provide an update method for resources unless it is not valuable for users to do so - - [Read-only resources](0101.md#read-only-resources) **must not** have an + - [Read-only resources](0101.mdx#read-only-resources) **must not** have an Update method - [Read-only singleton resources](0113.md#read-only-singleton-resources) **must not** have an Update method diff --git a/ipa/general/0108.md b/ipa/general/0108.md index 5e0052f..2ed7e04 100644 --- a/ipa/general/0108.md +++ b/ipa/general/0108.md @@ -13,7 +13,7 @@ resource. - APIs **should** provide a Delete method for resources unless it is not valuable for users to do so - - [Read-only resources](0101.md#read-only-resources) **must not** have a + - [Read-only resources](0101.mdx#read-only-resources) **must not** have a Delete method - [Singleton resources](0113.md) **must not** have a Delete method - The HTTP verb **must** be diff --git a/ipa/general/0109.md b/ipa/general/0109.md index 9b13547..4bb27fb 100644 --- a/ipa/general/0109.md +++ b/ipa/general/0109.md @@ -5,7 +5,7 @@ state: adopt # IPA-109: Custom Methods -[Resource-oriented design](0101.md) uses custom methods to provide a means to +[Resource-oriented design](0101.mdx) uses custom methods to provide a means to express arbitrary actions that are difficult to model using only the standard methods. diff --git a/ipa/general/0111.md b/ipa/general/0111.md index 80667fa..1e33f56 100644 --- a/ipa/general/0111.md +++ b/ipa/general/0111.md @@ -36,7 +36,7 @@ Fields **must** have a single owner, whether that is the client or the server. :::note For resources where all fields are server-owned, see -[read-only resources](0101.md#read-only-resources) and +[read-only resources](0101.mdx#read-only-resources) and [read-only singleton resources](0113.md#read-only-singleton-resources). ::: diff --git a/ipa/general/0127.md b/ipa/general/0127.md index 81619d6..49a304f 100644 --- a/ipa/general/0127.md +++ b/ipa/general/0127.md @@ -29,10 +29,10 @@ require extra, stricter guidance to support IaC tooling automation. ## Guidance - A resource **must** be strongly consistent with the **Resource-Oriented - Design** ([IPA-101](0101.md)) + Design** ([IPA-101](0101.mdx)) - A resource **must** have `CREATE`, `DELETE`, `GET`, `LIST` methods, except for [singleton resources](0113.md) and - [read-only resources](0101.md#read-only-resources) which **must** have `GET` + [read-only resources](0101.mdx#read-only-resources) which **must** have `GET` and `LIST` methods. - A resource **should not** have custom methods ([IPA-109](0109.md)). Any complementary functionality of a resource exposed through custom methods will @@ -55,7 +55,7 @@ error-prone development. ### Further Reading -- [IPA-101: Resource-Oriented Design](0101.md) +- [IPA-101: Resource-Oriented Design](0101.mdx) - [IPA-104: Get](0104.md) - [IPA-105: List](0105.md) - [IPA-106: Create](0106.md)