Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Clarify usage of parallel relationships #1677

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: gh-pages
Choose a base branch
from

Conversation

jelhan
Copy link
Contributor

@jelhan jelhan commented Oct 28, 2022

This PR tries to implement the agreement we came up in #1361.

I struggled with the wording to be honest. I found it difficult to distinguish between the different kinds of parallel relationships. For sake of clarity let me show them in code:

  1. Different relationships

    {
      "data": {
        "type": "articles",
        "id": "1",
        "relationships": {
          "author": {
            "data": {
              "type": "users",
              "id": "12"
            }
          },
         "editors": {
           "data": [
             {
               "type": "users",
               "id": "12"
             }
           ]
         }
      }
    }
  2. Intermediate resource

    {
      "data": {
        "type": "teams",
        "id": "1",
        "relationships": {
          "members": {
            "data": {
              "type": "teamMembers",
              "id": "12"
            }
          },
         "editors": {
           "data": [
             {
               "type": "teamMembers",
               "id": "13"
             }
           ]
         }
      },
      "included": [
        {
          "type": "teamMembers",
          "id": "12",
          "attributes": {
            "startDate": "2021-01-01",
            "endDate": "2021-12-31",
          },
          "relationships": {
            "user": {
              "data": {
                "type": "users",
                "id": "23"
              }
            }
          }
        },
        {
          "type": "teamMembers",
          "id": "13",
          "attributes": {
            "startDate": "2022-06-01",
            "endDate": "2022-12-31",
          },
          "relationships": {
            "user": {
              "data": {
                "type": "users",
                "id": "23"
              }
            }
          }
        }
      ]
    }
  3. Multiple, direct reference on same relationship

    {
      "data": {
        "type": "teams",
        "id": "1",
        "relationships": {
          "members": {
            "data": [
              {
                "type": "users",
                "id": "23",
                "meta": {
                  "startDate": "2021-01-01",
                  "endDate": "2021-12-31",
                }
              },
             {
                "type": "users",
                "id": "23",
                "meta": {
                  "startDate": "2022-06-01",
                  "endDate": "2022-12-31",
                }
              }
            ]
          }
        }
      }
    }

We only aim to discourage using the third type of parallel relationships. We recommend to use the second type instead. I'm not sure how well this is expressed in the current wording.

Open questions:

  1. Do we want to backport this recommendation to v1.1?
  2. Do we need a better wording to distinguish between relationships of a resource (author, editors) and references to resource in such relationships?

Closes #1361

@jelhan jelhan requested review from dgeb and gabesullice October 28, 2022 21:42
@stefanmoro
Copy link

Hey. We're using the JSON:API spec for some REST APIs at TIDAL, and have discussed the topic of parallell relationships. We have weighed some pros and cons for a specific case, and we will probably go for a solution where we support parallell relationships. The SHOULD NOT wording seems to strike a good balance in the specification.

In addition to adding the SHOULD NOT clarification on to-many relationships, would it make sense to also update the wording in the Updating to-many relationships section? Currently it reads, for POSTs:

If a given type and id is already in the relationship, the server MUST NOT add it again.

Changing MUST NOT to SHOULD NOT also for CRUD operations wrt parallell relationships, would keep the recommendation not to support parallell relationships, but also clarify that it is a valid implementation.

Thanks!

@jelhan
Copy link
Contributor Author

jelhan commented Aug 22, 2024

In addition to adding the SHOULD NOT clarification on to-many relationships, would it make sense to also update the wording in the Updating to-many relationships section? Currently it reads, for POSTs:

If a given type and id is already in the relationship, the server MUST NOT add it again.

Changing MUST NOT to SHOULD NOT also for CRUD operations wrt parallell relationships, would keep the recommendation not to support parallell relationships, but also clarify that it is a valid implementation.

I feel we should keep the MUST NOT in this case. One of the design goals of JSON:API is ensuring that a client can change the server's state without needing to know it. That eases consumers a lot. E.g. in case a request to add a relationship fails, the client can send the request again without needing to check before if the server has processed the request or not.

Maybe we should add a similar clarification that a DELETE request for a specific member must delete all references to that member on the relationship. And not only a subset of it if the resource is referenced multiple times.

I get that this requires using full replacement to manage parallel relationships. But in my perspective that's only one of the many drawbacks you get when using that discouraged pattern. The benefits for the recommended usage of relationships strongly outweigh that trade-off in my perspective.

Please note that the primarily reason for using SHOULD NOT instead of MUST NOT for parallel relationships is not introducing breaking changes. So far I have not seen any use case, which is better solved by parallel relationships than intermediate resources.

@stefanmoro
Copy link

stefanmoro commented Aug 23, 2024

Thank you for the swift and clear reply @jelhan, much appreciated.

First of all, I do agree that from a modelling perspective, using intermediate resources most likely represents the best solution. The reason we are leaning towards supporting parallell relationships are strictly practical. We are in essence retrofitting JSON:API on top of a previously existing system/API.

The way our data is currently structured, and the way we handle includes, would mean either quite a bit of work for us, or quite a compromise on performance and flexibility for clients, if we relied on intermediate resources. Also, each resource in the relationship will have a unique id in meta, making sure we have a way to uniquely address each relationship in the relationship object.

A few follow ups, just to make sure I understand correctly:

  1. Does using SHOULD NOT wording in general for to-many relationships, and MUST NOT wording for mutation operations for to-many relationships, imply that - strictly speaking - it is valid JSON:API for a server to expose a read-only to-many relationship containing parallell resources, but not a mutable one?

  2. Refs:

One of the design goals of JSON:API is ensuring that a client can change the server's state without needing to know it.

Got it, makes sense. What is your take on how compatible this goal is with the note in section 7.2.2.4, essentially saying that an implementation may choose to treat a to-many relationship as ordered (something we are also doing for several to-many relationships!). In essence, if an implementation chooses to treat a to-many relationship as ordered, doesn't the ordering of the to-many relationship represent a state that a client may need to relate to before mutating the relationship?

Thank you.

@jelhan
Copy link
Contributor Author

jelhan commented Aug 23, 2024

First of all, I do agree that from a modelling perspective, using intermediate resources most likely represents the best solution. The reason we are leaning towards supporting parallell relationships are strictly practical. We are in essence retrofitting JSON:API on top of a previously existing system/API.

The way our data is currently structured, and the way we handle includes, would mean either quite a bit of work for us, or quite a compromise on performance and flexibility for clients, if we relied on intermediate resources. Also, each resource in the relationship will have a unique id in meta, making sure we have a way to uniquely address each relationship in the relationship object.

I struggle imagining such a scenario. Would be great if you could elaborate. Maybe I'm missing relevant use cases for parallel relationships.

I assume it's just a matter of the serialization if representing it as an intermediate resource or not. If I get you right, you already have a unique identifier for each reference to a resource in that parallel relationship. In that case you already have an id for the intermediate resource. The type could be derived from the types of the related resources and the names of the relationship.

I expect you have a serialization such as the following today:

{
  "data": {
    "type": "teams",
    "id": "1",
    "relationships": {
      "members": {
        "data": [
          {
            "type": "teams_members",
            "id": "23",
            "meta": {
              "id": "101"
            }
          },
         {
            "type": "users",
            "id": "23",
            "meta": {
              "id": "102"
            }
          }
        ]
      }
    }
  }
}

What additional challenges due you face serializing it as the following:

{
  "data": {
    "type": "teams",
    "id": "1",
    "relationships": {
      "members": {
        "data": [
          {
            "type": "teamsMembers",
            "id": "101"
          },
         {
            "type": "teamsMembers",
            "id": "102"
          }
        ]
      }
    }
  },
  "included": [
    {
      "type": "teamsMembers",
      "id": "101",
      "relationships": {
        "user": {
          "type": "users",
          "id": "23"
        }
      }
    },
    {
      "type": "teamsMembers",
      "id": "102",
      "relationships": {
        "user": {
          "type": "users",
          "id": "23"
        }
      }
    }
  ]
}

Unless I missed something, I have just moved existing information around and made up a type for the intermediate resource. I know it is more complex if a unique identifier for the reference in the relationship does not exist yet. But that doesn't seem to be the case for you.

  1. Does using SHOULD NOT wording in general for to-many relationships, and MUST NOT wording for mutation operations for to-many relationships, imply that - strictly speaking - it is valid JSON:API for a server to expose a read-only to-many relationship containing parallell resources, but not a mutable one?

No. Mutation of parallel relationships are possible. But with the limitation that full replacement must be used in some cases:

  • Adding more than one reference to a resource to the same relationship requires full replacement. POST to a relationship link does not support adding a second (or more) references to the same resource to a relationship.
  • Removing only some references to a resource on the same relationship requires full replacement. DELETE to a relationship link will delete all references to the resources referenced by the provided resource identifier objects.

One of the design goals of JSON:API is ensuring that a client can change the server's state without needing to know it.

Got it, makes sense. What is your take on how compatible this goal is with the note in section 7.2.2.4, essentially saying that an implementation may choose to treat a to-many relationship as ordered (something we are also doing for several to-many relationships!). In essence, if an implementation chooses to treat a to-many relationship as ordered, doesn't the ordering of the to-many relationship represent a state that a client may need to relate to before mutating the relationship?

The JSON:API specification does not define how a client can change the order of an ordered to-many relationship. If sending the same request multiple times could lead to unexpected results depends on the specific implementation. I expect that most implementations does not have such a problem. E.g. if the order is mutated by a full-replacement of the relationship, the client describes the desired state in the request independently of the server state. The result is the same regardless if the request is successfully processed once or multiple times. The same is true if the implementation provides some way for a client to specify adding a reference in a specific position of a relationship. It's only an issue if an implementation allows mutation depending on current state (e.g. "move one up/down").

@stefanmoro
Copy link

stefanmoro commented Aug 25, 2024

Unless I missed something

You didn't miss anything, and your example illustrates exactly how we would model it using intermediate resources.

As I mentioned, the reasons we're leaning towards not using intermediate resources are strictly practical and a result of some design decisions/trade offs, and our current implementation. One concrete example is our logic that generates compound documents (included section). It currently doesn't support inclusion of nested paths. So, either we would have to rebuild our inclusion logic to support nested paths and generate full linkage (which, with the current setup we have, would introduce a performance decrease), or clients would have to make two consecutive calls to our backend in order to retrieve the same information. Basically, 1/ GET /teams/?include=members, and 2/ GET /teamMembers/?include=user.

No. Mutation of parallel relationships are possible. But with the limitation that ...

Thanks

The JSON:API specification does not define how a client can change the order of an ordered to-many relationship. If sending the same request multiple times could lead to unexpected results depends on the specific implementation. I expect that most implementations does not have such a problem. E.g. if the order is mutated by a full-replacement of the relationship, the client describes the desired state in the request independently of the server state. The result is the same regardless if the request is successfully processed once or multiple times. The same is true if the implementation provides some way for a client to specify adding a reference in a specific position of a relationship. It's only an issue if an implementation allows mutation depending on current state (e.g. "move one up/down").

Thanks, and yes - it is the "It's only an issue if an implementation allows mutation depending on current state (e.g. "move one up/down")." case I had in mind. "Move up/down" could be one scenario. "Insert after" could be another.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Support for parallel relationships
2 participants