diff --git a/aip/general/0124/aip.md.j2 b/aip/general/0124/aip.md.j2 new file mode 100644 index 00000000..f3252819 --- /dev/null +++ b/aip/general/0124/aip.md.j2 @@ -0,0 +1,64 @@ +# Resource association + +APIs sometimes have resource hierarchies that can not be cleanly expressed in +the usual tree structure. For example, a resource may have a many-to-one +relationship with two other resource types instead of just one. Alternatively, +a resource may have a many-to-many relationship with another resource type. + +## Guidance + +A resource **must** have at most one canonical parent, and `GET` operations to +read collections **must not** require two distinct "parents" to work. + +### Multiple many-to-one associations + +If a resource has a many-to-one relationship with multiple resource types, it +**must** choose at most one of them to be the canonical parent. The resource +**may** be associated with other resources through other fields on the +resource. + +{% tab proto -%} + +{% sample 'many_to_one.proto', 'message Book' %} + +{% endtabs %} + +When listing resources (see AIP-132) with multiple associations in this way, +the RPC **must** treat the `string parent` field, and **must not** add +additional required arguments; additionally, the operation **should** include a +`string filter` field (see AIP-160) that allows users to filter by other +resource associations. + +### Many-to-many associations + +Many-to-many associations are less common in APIs than they are in relational +databases, in part because they are more difficult to model and present over +network interfaces. + +An API **may** contain many-to-many relationships, and **should** use a +repeated field containing a list of resource names, following the principles +described for repeated fields in AIP-144. + +{% tab proto -%} + +{% sample 'many_to_many.proto', 'message Book' %} + +{% endtabs %} + +**Note:** See AIP-144 for more information on repeated fields, including how to +handle common issues such as atomic changes. + +If the use of a repeated field is too restrictive, or if more metadata is +required along with the association, an API **may** model a many-to-many +relationship using a sub-resource with two one-to-many associations. + +{% tab proto -%} + +{% sample 'subresource.proto', 'message BookAuthor' %} + +{% endtabs %} + +**Note:** Using subresources to model an association between resources is only +recommended if additional metadata is required in the relationship, or if the +restrictions around the use of a repeated field preclude the use of that +approach. diff --git a/aip/general/0124/aip.yaml b/aip/general/0124/aip.yaml new file mode 100644 index 00000000..fd51baa2 --- /dev/null +++ b/aip/general/0124/aip.yaml @@ -0,0 +1,7 @@ +--- +id: 124 +state: approved +created: 2020-03-20 +placement: + category: resource-design + order: 40 diff --git a/aip/general/0124/many_to_many.proto b/aip/general/0124/many_to_many.proto new file mode 100644 index 00000000..72a7760e --- /dev/null +++ b/aip/general/0124/many_to_many.proto @@ -0,0 +1,56 @@ +// Copyright 2021 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +import "google/api/resource.proto"; + +// A representation of an individual person who writes a book. +message Author { + option (google.api.resource) = { + type: "library.googleapis.com/Author" + pattern: "publishers/{publisher}/authors/{author}" + }; + + // The name of the author. + // Format: publishers/{publisher}/authors/{author} + string name = 1; + + // The author's given name (in Western culture: "first name"). + string given_name = 2; + + // The author's family name (in Western culture: "last name"). + string family_name = 3; +} + +// A representation of a single book. +message Book { + option (google.api.resource) = { + type: "library.googleapis.com/Book" + pattern: "publishers/{publisher}/books/{book}" + }; + + // The name of the book. + // Format: publishers/{publisher}/books/{book} + string name = 1; + + // The resource names for the authors of the book. + // Format: publishers/{publisher}/authors/{author} + repeated string authors = 2 [(google.api.resource_reference) = { + type: "library.googleapis.com/Author" + }]; + + // The rating assigned to the book. + float rating = 3; +} diff --git a/aip/general/0124/many_to_one.proto b/aip/general/0124/many_to_one.proto new file mode 100644 index 00000000..598f03c9 --- /dev/null +++ b/aip/general/0124/many_to_one.proto @@ -0,0 +1,56 @@ +// Copyright 2021 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +import "google/api/resource.proto"; + +// A representation of an individual person who writes a book. +message Author { + option (google.api.resource) = { + type: "library.googleapis.com/Author" + pattern: "publishers/{publisher}/authors/{author}" + }; + + // The name of the author. + // Format: publishers/{publisher}/authors/{author} + string name = 1; + + // The author's given name (in Western culture: "first name"). + string given_name = 2; + + // The author's family name (in Western culture: "last name"). + string family_name = 3; +} + +// A representation of a single book. +message Book { + option (google.api.resource) = { + type: "library.googleapis.com/Book" + pattern: "publishers/{publisher}/books/{book}" + }; + + // The name of the book. + // Format: publishers/{publisher}/books/{book} + string name = 1; + + // The resource name for the author of the book. + // Format: publishers/{publisher}/authors/{author} + string author = 2 [(google.api.resource_reference) = { + type: "library.googleapis.com/Author" + }]; + + // The rating assigned to the book. + float rating = 3; +} diff --git a/aip/general/0124/subresource.proto b/aip/general/0124/subresource.proto new file mode 100644 index 00000000..5da36e98 --- /dev/null +++ b/aip/general/0124/subresource.proto @@ -0,0 +1,73 @@ +// Copyright 2021 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +import "google/api/resource.proto"; + +// A representation of an individual person who writes a book. +message Author { + option (google.api.resource) = { + type: "library.googleapis.com/Author" + pattern: "publishers/{publisher}/authors/{author}" + }; + + // The name of the author. + // Format: publishers/{publisher}/authors/{author} + string name = 1; + + // The author's given name (in Western culture: "first name"). + string given_name = 2; + + // The author's family name (in Western culture: "last name"). + string family_name = 3; +} + +// A representation of a single book. +message Book { + option (google.api.resource) = { + type: "library.googleapis.com/Book" + pattern: "publishers/{publisher}/books/{book}" + }; + + // The name of the book. + // Format: publishers/{publisher}/books/{book} + string name = 1; + + // The rating assigned to the book. + float rating = 2; +} + +// A representation of the link between a particular book and a +// particular author. +message BookAuthor { + // The resource pattern for BookAuthor indicates that Book is the + // canonical parent. + option (google.api.resource) = { + type: "library.googleapis.com/BookAuthor" + pattern: "publishers/{publisher}/books/{book}/authors/{book_author}" + }; + + // The resource name for the book-author association. + // Format: publishers/{publisher}/books/{book}/authors/{book_author} + string name = 1; + + // The resource name for the author. + // Format: publishers/{publisher}/authors/{author} + string author = 2 [(google.api.resource_reference) = { + type: "library.googleapis.com/Author" + }]; + + // Other fields... +}