diff --git a/ipa/general/0101.mdx b/ipa/general/0101.mdx index 32dd9c2..dfa5c95 100644 --- a/ipa/general/0101.mdx +++ b/ipa/general/0101.mdx @@ -9,7 +9,7 @@ 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 + [individually named resources](0102.mdx) (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 diff --git a/ipa/general/0102.md b/ipa/general/0102.md deleted file mode 100644 index f756df2..0000000 --- a/ipa/general/0102.md +++ /dev/null @@ -1,89 +0,0 @@ ---- -id: 102 -state: adopt ---- - -# IPA-102: Resource Identifiers - -Most APIs expose resources (their primary nouns) that users can create, -retrieve, and manipulate. Additionally, resources are named meaning each -resource has a unique identifier that API consumers use to reference that -resource. - -## Guidance - -- The full resource identifier is a URI without transport protocols (schemeless) - - Fully qualified paths -- All resource identifiers defined by an API **must** be unique - - Resource names are formatted according to the - [URI path schema](https://datatracker.ietf.org/doc/html/rfc3986#appendix-A) -- Resource identifiers **must** use the slash (/) character to separate - individual segments of the resource identifier - - Double slashes (//) **must not** be used -- Resource identifier components **should** alternate between: - - Collection identifiers (example: groups, clusters, orgs, users) - - Collection identifiers **must** be in `camelCase` - - Collection identifiers **must** begin with a lowercase letter and contain - only ASCII letters and numbers (`/[a-z][a-zA-Z0-9]*/`). - - Collection identifiers **must** be plural - - In situations where there is no plural word ("info"), or where the - singular and plural terms are the same ("moose"), the non-pluralized - (singular) form is correct. - - Resource IDs (example: `groupId`, `clusterName`, `orgId`) - - Resource IDs **should** be server-generated unique identifiers rather than - human-readable, client-provided identifiers - - Server-generated unique identifiers (e.g., UUIDs, auto-generated IDs) - are immutable and globally unique - - Human-readable identifiers (e.g., names, types) may change over time or - have uniqueness constraints that are difficult to maintain - - In some cases, API producers might decide to use a human-readable - identifier (e.g., `clusterName`), however using a system-generated - unique identifier is recommended for long-term stability and flexibility - - Resource IDs **should** follow the format `Id` - - Where `` is the singular form of the collection identifier - - Example: for collection `groups`, the resource ID should be `groupId` - - Resource IDs **must** be in `camelCase` - - The resource ID used in the resource identifier (as the URI path - parameter) **must** match the field name used in the resource - representation - - This ensures consistency between the resource identifier and the - resource representation - - Example: if the resource identifier uses `groupId`, the resource itself - **must** have a field named `groupId` -- Resource identifiers **should not** use abbreviations - - Unless the abbreviation is well understood, for example, IP, AWS, TCP -- Resource identifier **must not** include file extensions such as `.gz`,`.csv`, - `.json` - - The file extension **must** be only included as - [media type](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/MIME_types) - in the - [Accept Header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Accept). - - Examples: - - `Accept: application/zip` - - `Accept: application/json` - - `Accept: application/xml` - - `Accept: application/gzip` - -Example - -- Group identifier `/groups/{groupId}` -- Cluster identifier `/groups/{groupId}/clusters/{clusterName}` -- User collection identifier `/orgs/{orgId}/users` - -### Nested Collections - -If a resource identifier contains multiple levels of a hierarchy and a parent -collection's name is used as a prefix for the child resource's name, the child -collection's name **may** omit the prefix. - -:::note - -Relationships between resources expressed as nested collections or hierarchical -relationships have certain implications that API producers need to consider - -::: - -- Nested collections imply a cascade effect -- Deleting a parent **must** delete associated children -- Access to the parent **may** imply access to children -- Children **must not** belong to multiple parents diff --git a/ipa/general/0102.mdx b/ipa/general/0102.mdx new file mode 100644 index 0000000..f97d59b --- /dev/null +++ b/ipa/general/0102.mdx @@ -0,0 +1,996 @@ +--- +id: 102 +state: adopt +--- + +# IPA-102: Resource Identifiers + +Most APIs expose resources (their primary nouns) that users can create, +retrieve, and manipulate. Additionally, resources are named meaning each +resource has a unique identifier that API consumers use to reference that +resource. + +## Guidance + +- The full resource identifier is a URI without transport protocols (schemeless) + - Fully qualified paths +- All resource identifiers defined by an API **must** be unique + - Resource names are formatted according to the + [URI path schema](https://datatracker.ietf.org/doc/html/rfc3986#appendix-A) +- Resource identifiers **must** use the slash (/) character to separate + individual segments of the resource identifier + - Double slashes (//) **must not** be used +- Resource identifier components **should** alternate between: + - Collection identifiers (example: groups, clusters, orgs, users) + - Collection identifiers **must** be in `camelCase` + - Collection identifiers **must** begin with a lowercase letter and contain + only ASCII letters and numbers (`/[a-z][a-zA-Z0-9]*/`). + - Collection identifiers **must** be plural + - In situations where there is no plural word ("info"), or where the + singular and plural terms are the same ("moose"), the non-pluralized + (singular) form is correct. + - Resource IDs (example: `groupId`, `clusterName`, `orgId`) + - Resource IDs **should** be server-generated unique identifiers rather than + human-readable, client-provided identifiers + - Server-generated unique identifiers (e.g., UUIDs, auto-generated IDs) + are immutable and globally unique + - Human-readable identifiers (e.g., names, types) may change over time or + have uniqueness constraints that are difficult to maintain + - In some cases, API producers might decide to use a human-readable + identifier (e.g., `clusterName`), however using a system-generated + unique identifier is recommended for long-term stability and flexibility + - Resource IDs **should** follow the format `Id` + - Where `` is the singular form of the collection identifier + - Example: for collection `groups`, the resource ID should be `groupId` + - Resource IDs **must** be in `camelCase` + - The resource ID used in the resource identifier (as the URI path + parameter) **must** match the field name used in the resource + representation + - This ensures consistency between the resource identifier and the + resource representation + - Example: if the resource identifier uses `groupId`, the resource itself + **must** have a field named `groupId` +- Resource identifiers **should not** use abbreviations + - Unless the abbreviation is well understood, for example, IP, AWS, TCP +- Resource identifier **must not** include file extensions such as `.gz`,`.csv`, + `.json` + - The file extension **must** be only included as + [media type](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/MIME_types) + in the + [Accept Header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Accept). + - Examples: + - `Accept: application/zip` + - `Accept: application/json` + - `Accept: application/xml` + - `Accept: application/gzip` + + + + + +All resource identifiers defined by an API **must** be unique, with resource +names formatted according to the URI path schema. + + + +```yaml +paths: + /api/atlas/v2/groups/{groupId}/clusters: + get: + operationId: listClusters + /api/atlas/v2/groups/{groupId}/clusters/{clusterName}: + get: + operationId: getCluster +``` + + + Each path resolves to exactly one resource identifier, so no two operations + collide on the same identifier. + + + + + + +```yaml +paths: + /api/atlas/v2/groups/{groupId}/clusters: + get: + operationId: listClusters + /api/atlas/v2/groups/{groupId}/clusters: + post: + operationId: createCluster +``` + + + The same resource identifier is declared twice, so the identifier no longer + uniquely references a single resource definition. + + + + + + + Enumerate every key under `paths` to collect all resource identifiers + defined by the API. + + + Normalize each identifier (ignoring path-parameter names) and check for any + two paths that resolve to the same resource identifier. + + + Confirm each identifier conforms to the URI path schema (RFC 3986 Appendix + A). + + + Report any duplicate or malformed resource identifier as a violation. + + + + + + + +Resource identifiers **must** use the slash (/) character to separate individual +segments of the resource identifier. + + + +```yaml +paths: + /api/atlas/v2/groups/{groupId}/clusters/{clusterName}: + get: + operationId: getCluster +``` + + + Every segment boundary is a single slash, so the identifier parses into a + clean, predictable hierarchy of segments. + + + + + + +```yaml +paths: + /api/atlas/v2/groups/{groupId}/clusters.{clusterName}: + get: + operationId: getCluster +``` + + + Using a period instead of a slash between `clusters` and `{clusterName}` + prevents the identifier from being parsed as separate hierarchy segments. + + + + + + + For each key under `paths`, split the identifier and confirm that segments + are delimited only by the slash (/) character. + + + Flag any segment boundary that uses a different delimiter (for example a + period, comma, or semicolon) instead of a slash. + + + + + + + +Double slashes (//) **must not** be used to separate segments of a resource +identifier. + + + +```yaml +paths: + /api/atlas/v2/groups/{groupId}/clusters: + get: + operationId: listClusters +``` + + + Single slashes produce no empty segments, so every segment of the identifier + is meaningful. + + + + + + +```yaml +paths: + /api/atlas/v2/groups/{groupId}//clusters: + get: + operationId: listClusters +``` + + + The `//` introduces an empty path segment, which is ambiguous and not a valid + resource identifier. + + + + + + + + +Resource identifier components **should** alternate between collection +identifiers and resource IDs. + + + +```yaml +paths: + /api/atlas/v2/groups/{groupId}/clusters/{clusterName}: + get: + operationId: getCluster +``` + + + Segments strictly alternate collection identifier, path parameter, collection + identifier, path parameter — so the hierarchy is unambiguous. + + + + + + +```yaml +paths: + /api/atlas/v2/groups/{groupId}/{clusterName}: + get: + operationId: getCluster +``` + + + Two path parameters appear back to back with no intervening collection + identifier, breaking the resource-name / path-param alternation. + + + + + + + + +Collection identifiers **must** be in `camelCase`. + + + +```yaml +paths: + /api/atlas/v2/groups/{groupId}/serverlessInstances: + get: + operationId: listServerlessInstances +``` + + + `serverlessInstances` is camelCase, so the collection identifier is consistent + with the API-wide naming convention. + + + + + + +```yaml +paths: + /api/atlas/v2/groups/{groupId}/serverless_instances: + get: + operationId: listServerlessInstances +``` + + + `serverless_instances` uses snake_case, which is not camelCase and breaks the + naming convention for collection identifiers. + + + + + + + + +Collection identifiers **must** begin with a lowercase letter and contain only +ASCII letters and numbers (`/[a-z][a-zA-Z0-9]*/`). + + + +```yaml +paths: + /api/atlas/v2/groups/{groupId}/clusters: + get: + operationId: listClusters +``` + + + `clusters` begins with a lowercase letter and contains only ASCII letters, + matching the required pattern. + + + + + + +```yaml +paths: + /api/atlas/v2/groups/{groupId}/Clusters: + get: + operationId: listClusters +``` + + + `Clusters` begins with an uppercase letter, violating the + `/[a-z][a-zA-Z0-9]*/` pattern required for collection identifiers. + + + + + + + + +Collection identifiers **must** be plural, except where there is no plural form +or the singular and plural terms are the same, in which case the singular form +is correct. + + + +```yaml +paths: + /api/atlas/v2/groups/{groupId}/clusters: + get: + operationId: listClusters +``` + + + `clusters` is the plural form of the noun, signaling that the segment + identifies a collection rather than a single resource. + + + + + + +```yaml +paths: + /api/atlas/v2/groups/{groupId}/cluster: + get: + operationId: listClusters +``` + + + `cluster` is singular even though it names a collection of clusters, which + obscures the difference between a collection and an individual resource. + + + + + + + For each key under `paths`, identify the collection-identifier segments + (non-parameter segments that name a collection). + + + Check whether each collection identifier is a plural noun. + + + For each apparent exception, confirm the noun has no distinct plural form + (e.g. "info") or that its singular and plural are identical (e.g. "moose") + before accepting the singular spelling. + + + Report any singular collection identifier that does not qualify for the + exception as a violation. + + + + + + + +Resource IDs **should** be server-generated unique identifiers rather than +human-readable, client-provided identifiers, for long-term stability and +flexibility. + + + +```yaml +paths: + /api/atlas/v2/groups/{groupId}: + get: + operationId: getGroup +``` + + + `groupId` is a server-generated, immutable, globally unique identifier, so the + reference stays stable even if the resource is renamed. + + + + + + +```yaml +paths: + /api/atlas/v2/groups/{groupName}: + get: + operationId: getGroup +``` + + + `groupName` is a human-readable, client-provided identifier that can change + over time or carry uniqueness constraints that are hard to maintain. + + + + + + + For each resource identifier under `paths`, locate the resource-ID path + parameters (the segments inside curly braces). + + + Determine whether each resource ID is a server-generated identifier (e.g. + `groupId`, an auto-generated ID or UUID) or a human-readable, + client-supplied value (e.g. a name). + + + For any human-readable identifier, check whether a documented rationale + justifies using it instead of a system-generated unique identifier. + + + Report any unjustified human-readable resource ID as a deviation. + + + + + + + +Resource IDs **should** follow the format `Id`, where +`` is the singular form of the collection identifier. + + + +```yaml +paths: + /api/atlas/v2/groups/{groupId}: + get: + operationId: getGroup +``` + + + The collection is `groups`, so its resource ID is `groupId` — the singular + collection name plus the `Id` suffix, making the relationship obvious. + + + + + + +```yaml +paths: + /api/atlas/v2/groups/{id}: + get: + operationId: getGroup +``` + + + A bare `id` does not follow the `Id` format, so it is unclear + which collection the identifier belongs to. + + + + + + + For each resource identifier under `paths`, pair each resource-ID path + parameter with its preceding collection identifier. + + + Compute the expected resource-ID name as the singular form of the collection + identifier followed by `Id` (e.g. `groups` → `groupId`). + + + Compare the actual path-parameter name against the expected name and report + any that do not follow the `Id` format. + + + + + + + +Resource IDs **must** be in `camelCase`. + + + +```yaml +paths: + /api/atlas/v2/groups/{groupId}: + get: + operationId: getGroup + parameters: + - name: groupId + in: path + required: true + schema: + type: string +``` + + + `groupId` is camelCase, keeping resource-ID naming consistent with field + naming across the API. + + + + + + +```yaml +paths: + /api/atlas/v2/groups/{group_id}: + get: + operationId: getGroup + parameters: + - name: group_id + in: path + required: true + schema: + type: string +``` + + + `group_id` uses snake_case, which is not camelCase and breaks consistency with + the rest of the API surface. + + + + + + + Collect every path parameter that serves as a resource ID from the + `parameters` lists. + + + Check each resource-ID name against the camelCase convention (lowercase + first letter, no separators such as underscores or hyphens). + + + Report any resource ID that is not camelCase as a violation. + + + + + + + +The resource ID used in the resource identifier (as the URI path parameter) +**must** match the field name used in the resource representation. + + + +```yaml +paths: + /api/atlas/v2/groups/{groupId}: + get: + operationId: getGroup +components: + schemas: + Group: + properties: + groupId: + type: string + name: + type: string +``` + + + The path parameter `groupId` and the representation field `groupId` use the + same name, so the identifier and the resource body stay consistent. + + + + + + +```yaml +paths: + /api/atlas/v2/groups/{groupId}: + get: + operationId: getGroup +components: + schemas: + Group: + properties: + id: + type: string + name: + type: string +``` + + + The identifier uses `groupId` but the representation exposes the field as + `id`, so a client cannot reliably map the path parameter to a body field. + + + + + + + For each resource identifier under `paths`, note the resource-ID path + parameter name. + + + Locate the schema that represents that resource in `components.schemas`. + + + Confirm the representation schema contains a field whose name matches the + path-parameter resource ID exactly. + + + Report any mismatch between the path-parameter resource ID and the + representation field name as a violation. + + + + + + + +Resource identifiers **should not** use abbreviations, unless the abbreviation +is well understood (for example, IP, AWS, TCP). + + + +```yaml +paths: + /api/atlas/v2/groups/{groupId}/networkPermissionEntries: + get: + operationId: listNetworkPermissionEntries +``` + + + The collection identifier spells out `networkPermissionEntries` in full, + keeping the identifier readable for all consumers. + + + + + + +```yaml +paths: + /api/atlas/v2/groups/{groupId}/netPermEntries: + get: + operationId: listNetworkPermissionEntries +``` + + + `netPermEntries` abbreviates "network" and "permission" into terms that are + not widely understood, hurting readability. + + + + + + + For each key under `paths`, inspect each non-parameter segment of the + resource identifier. + + + Identify any segment that uses an abbreviation rather than the full word. + + + For each abbreviation, decide whether it is a well-understood term (e.g. IP, + AWS, TCP) or an obscure shortening. + + + Report any abbreviation that is not well understood as a deviation. + + + + + + + +Resource identifiers **must not** include file extensions such as `.gz`, `.csv`, +or `.json`. + + + +```yaml +paths: + /api/atlas/v2/groups/{groupId}/clusters/{clusterName}/backup: + get: + operationId: getBackup +``` + + + The identifier names the resource without a file extension; the desired + representation is negotiated separately via the Accept header. + + + + + + +```yaml +paths: + /api/atlas/v2/groups/{groupId}/clusters/{clusterName}/backup.json: + get: + operationId: getBackup +``` + + + The `.json` extension encodes a representation format into the resource + identifier, coupling the resource to one media type instead of using content + negotiation. + + + + + + + For each key under `paths`, inspect each segment of the resource identifier + for a trailing file extension (e.g. `.gz`, `.csv`, `.json`). + + + Report any segment that carries a file extension as a violation, since + format selection belongs in the Accept header, not the identifier. + + + + + + + +When a representation format is needed, the file extension **must** be included +only as a media type in the Accept header (for example, +`Accept: application/json`, `Accept: application/gzip`), never in the resource +identifier. + + + + + +Example + +- Group identifier `/groups/{groupId}` +- Cluster identifier `/groups/{groupId}/clusters/{clusterName}` +- User collection identifier `/orgs/{orgId}/users` + +### Nested Collections + +If a resource identifier contains multiple levels of a hierarchy and a parent +collection's name is used as a prefix for the child resource's name, the child +collection's name **may** omit the prefix. + +:::note + +Relationships between resources expressed as nested collections or hierarchical +relationships have certain implications that API producers need to consider + +::: + +- Nested collections imply a cascade effect +- Deleting a parent **must** delete associated children +- Access to the parent **may** imply access to children +- Children **must not** belong to multiple parents + + + + + +If a resource identifier contains multiple levels of a hierarchy and a parent +collection's name is used as a prefix for the child resource's name, the child +collection's name **may** omit the prefix. + + + +```yaml +paths: + /api/atlas/v2/clusters/{clusterName}/nodes: + get: + operationId: listClusterNodes +``` + + + Under the `clusters` parent the child collection is simply `nodes`; the + `cluster` prefix is redundant and omitting it keeps the identifier concise. + + + + + + +```yaml +paths: + /api/atlas/v2/clusters/{clusterName}/clusterNodes: + get: + operationId: listClusterNodes +``` + + + `clusterNodes` repeats the parent `clusters` prefix that the hierarchy already + conveys, making the identifier needlessly verbose. + + + + + + + For each nested resource identifier under `paths`, identify the parent + collection and each child collection name. + + + Check whether a child collection name redundantly repeats the parent + collection's name as a prefix. + + + Note that omitting such a redundant prefix is permitted; do not flag the + concise form as a violation. + + + + + + + +Deleting a parent resource **must** delete its associated child resources +(nested collections imply a cascade effect). + + + +```yaml +paths: + /api/atlas/v2/groups/{groupId}: + delete: + operationId: deleteGroup + description: >- + Deletes the specified group and all clusters, network peering + connections, and other child resources it contains. +``` + + + Deleting the parent group removes its nested children, so no orphaned child + resources are left behind. + + + + + + +```yaml +paths: + /api/atlas/v2/groups/{groupId}: + delete: + operationId: deleteGroup + description: >- + Deletes the specified group. Clusters within the group are left running + and must be deleted separately. +``` + + + Leaving child clusters behind after deleting the parent group orphans them, + breaking the cascade implied by the nested-collection hierarchy. + + + + + + + Identify the Delete operation for a parent resource that owns nested child + collections. + + + Inspect the delete handler's source code (or backing service logic) to + determine what happens to the parent's child resources. + + + Confirm that deleting the parent also deletes all associated children rather + than leaving them orphaned. + + + Report any parent Delete operation that does not cascade to its children as + a violation. + + + + + + + +Access to a parent resource **may** imply access to its child resources. + + + + + +A child resource **must not** belong to multiple parents. + + + +```yaml +paths: + /api/atlas/v2/groups/{groupId}/clusters/{clusterName}: + get: + operationId: getCluster +``` + + + Each cluster is reachable only beneath its single owning group, so every child + resource has exactly one parent. + + + + + + +```yaml +paths: + /api/atlas/v2/groups/{groupId}/clusters/{clusterName}: + get: + operationId: getClusterByGroup + /api/atlas/v2/orgs/{orgId}/clusters/{clusterName}: + get: + operationId: getClusterByOrg +``` + + + The same `clusters/{clusterName}` child is nested under both `groups` and + `orgs`, giving the child two parents and making ownership ambiguous. + + + + + + + For each child collection under `paths`, collect every parent collection it + is nested beneath across all resource identifiers. + + + Determine whether the same child collection appears under more than one + distinct parent collection. + + + Report any child resource that belongs to multiple parents as a violation. + + + + + +