Skip to content

Question: How to handle 1:n relationships ? #301

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
zto-sbenning opened this issue Feb 3, 2020 · 3 comments
Open

Question: How to handle 1:n relationships ? #301

zto-sbenning opened this issue Feb 3, 2020 · 3 comments
Labels
enhancement New feature or request question Further information is requested

Comments

@zto-sbenning
Copy link

Hello there .

Thank you for the awesome work here !

Bad english here, TL;DR: Is their some plan for supporting 1:n relationships in openapi-to-graphql in the futur ?

In regard of this part of the documentation

To create nested object types for arrays, you will need to keep the following in mind.

Continuing from the previous example, let's say that there is a third operation called GET /friends/{userId} which would return an array of users, specifically the friends of a particular user. Furthermore, let's say you wanted to run the following query, which would allow you to get all the employers of Alan's friends:

query {
  friends(userId: "Alan") {
    currentEmployerId
    employer {
      name
    }
  }
}

If this was like the previous case, you would simply define a link from GET /friends/{userId} to GET /employers/{employerId}. However, this is impossible because of the current specification. This is because this operation returns an array rather than an object and the current specification does not provide a way to access individual elements of an array.

Nevertheless, OpenAPI-to-GraphQL can still create a nested relationship. This is because OpenAPI-to-GraphQL reuses object types. If GET /friends/{userId} returns an array of User object types, then each of those users will take on the links defined in other operations that return User object types. In other words, because GET /friends/{userId} returns an array of User object types and GET /users/{userId}, which also returns a User object type, has a link to GET /employers/{employerId}, you will still be able to get all the employers of a user's friends because of the shared type.

I understand that because of OAI/OpenAPI-Specification#1327 and OAI/OpenAPI-Specification#1305, we cannot resolve 1:n nested relationships (with OAS Link object) in openapi-to-graphql.

Am I right here ? If no, can you please point me to a solution reference ?

Is their some plan for supporting this in openapi-to-graphql in the futur ?

Would it be a good approch to use an OAS extention object to define these relationships to openapi-to-graphql ? This extention (like a x-Links property on the OAS response object) would be like the OAS Link object, but it would use JSON Path (or another way) to parse the parameters values over JSON Pointer ?
Or would it be a better approch to add an extention object at the Schema object level to express those relationships?

Best regards,

@Alan-Cha
Copy link
Collaborator

Alan-Cha commented Feb 3, 2020

Hi! We do support 1:n relationships but it requires a kind of work around as you pointed out from our documentation, whereby a 1:1 link is "borrowed" to form a 1:n link.

Thank you for referencing the related issues. Actually, this comment is particularly interesting as this is similar to how we handle links. When OtG is creating types, it is comparing schemas (rather than comparing operations) and as a result, the links are associated with particular schemas rather than operations.

Does our solution not work for your use case? I understand if you think it's somewhat convoluted or contrived.

We currently are not exploring new ways of expressing 1:n relationships however we are open to discussion. In general, we try to stick with the features in the official OAS but I think finding an easier way to define 1:n relationships would be an extremely beneficial for both OAS and OtG and is indeed something that we are interested in.

@Alan-Cha Alan-Cha added enhancement New feature or request question Further information is requested labels Feb 3, 2020
@zto-sbenning
Copy link
Author

Hi! Thank you so much for your quick response !

I cannot figure out, how to express my use case, with your solution.

Because of this clever way to associate Links to graphql types over rest operations, I have the feeling that you can express n * (1:1) relathionships -- Because you know that one user is associated to one employee, you can derive that rule when fetching multiple user.

Not being silly here ! I really think this is brilliant and so more powerfull / usefull than other packages around there. However, I don't think my use case fits that way..

I have both user and role entities.
A role entity, only has id and name properties.
A user entity has id, email and roles properties -- with roles being an array of role's ids.

I have basic READ operations for both entities defined in my OAS.

I would love to be able to fetch an user's role's names in one go to the graphql endpoint, from a query returning one (or many :p ) users. But I cannot figure out how can I do / workaround this ?

I think you got my point, but for reference, here is the OAS part describing my use case (links missing), and the graphql query I would love to perform.

{
    "components": {
        "schemas": {
            "Role": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "string",
                        "nullable": false
                    },
                    "name": {
                        "type": "string",
                        "nullable": false
                    }
                }
            },
            "Roles": {
                "type": "array",
                "items": {
                    "$ref": "#/components/schemas/Role"
                }
            },
            "User": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "string",
                        "nullable": false
                    },
                    "email": {
                        "type": "string",
                        "nullable": false
                    },
                    "roles": {
                        "type": "array",
                        "items": {
                            "type": "string",
                            "nullable": false
                        },
                        "nullable": false
                    }
                }
            },
            "Users": {
                "type": "array",
                "items": {
                    "$ref": "#/components/schemas/User"
                }
            }
        }
    },
    "paths": {
        "/roles": {
            "get": {
                "responses": {
                    "200": {
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Roles"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/roles/{id}": {
            "get": {
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Role"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/users": {
            "get": {
                "responses": {
                    "200": {
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Users"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/users/{id}": {
            "get": {
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/User"
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}
{
  user(id: "xxx") {
    email,
    # A name for the link here
    rolesByIdArray {
      name
    }
  }
}

Again, thank you so much for your work here. Glad that you are also interested in ways to better express those relationships.

Best regards,

@Alan-Cha
Copy link
Collaborator

@zto-sbenning Ah so sorry for the delayed response! I got absolutely buried in work and I had to put OtG on the side for a while.

I see! Yes, for this particular case, I don't think the proposed methods would help you. I think this is more evidence that we need to come up with a better/custom solution to allow for links utilizing arrays.

One potential workaround is to define a new operation that can retrieve a selection of roles from an array of ids. Then you can just create a link on GET /users/{id} that will call that new operation.

If you cannot change your API, you can still define the new operation in the schema, but you will need to use the customResolvers option in order to define the logic.

One ugly thing about this strategy is that you will need to serialize the ids and parse it afterwards.


To start the discussion about creating a custom solution focused on supporting links utilizing arrays...

There seems to be two basic cases.

In one case, the response data is a list of objects and you want to extract a fields from each object, which is used to call a set of operations.

For example, there might be an operation GET /users which returns a set of Users. Each User object contains an email field, which could be used to call some other operation.

The other case, the response data is a single object, and you want to extract an array field, which is used to call a set of operations.

This is more similar to your use case. GET /users/{id} which returns a single User. This User object contains an ids field, which contains a number of ids that can be used to fetch a number of Roles.

Lastly, there is a complex case, which is a combination of both.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request question Further information is requested
Projects
None yet
Development

No branches or pull requests

2 participants