julienvincent / modelizr

Generate GraphQL queries from models that can be mocked and normalized.
http://julienvincent.github.io/modelizr
176 stars 6 forks source link

Additional/Alternative Query Syntax Proposal #2

Closed alexisvincent closed 8 years ago

alexisvincent commented 8 years ago

The current 'query language' conflates queries with the type system and is difficult to read due to field names (as mutator/modifier) being at the bottom. This could be simplified by using a json tree and leveraging the type system. Thought I would give a proper example after our discussion earlier.

current implementation

import { query } from 'modelizr'
import { event, organization, user, book } from './schema.js'

query(
    event(
        organization(
            user(
                book(
                    user(
                        company(
                            user().as('father')
                        ).as('employer')
                    ).as('author'),
                    book(
                        user(
                            user(
                                user().as('mother')
                            ).as('guardian')
                        ).as('author'),
                    ).as('inspiredBy')
                ).as('favoriteBook')
            ).as('founder')
        ).as('company')
    )
)

proposal

import { query } from 'modelizr'
import { event } from './schema.js'

query(
    event({
        company: {
            founder: {
                favoriteBook: {
                    author: {
                        employer: {
                            father: {}
                        }
                    },
                    inspiredBy: {
                        author: {
                            guardian: {
                                mother: {}
                            }
                        }
                    }
                }
            }
        }
    })
)

and alternatively for the adventurous

import { query } from 'modelizr'
import { event } from './schema.js'

query(
    event({
        company: {
            founder: {
                favoriteBook: {
                    author: {
                        employer: {
                            father: {}}},
                    inspiredBy: {
                        author: {
                            guardian: {
                                mother: {}}}}}}}})
)
julienvincent commented 8 years ago

Although your proposal does solve the problem of readability, you lose the ability to apply any modifiers to the nested query. That would be very limiting.

An alternative to this would be to have a way to alias models. Something like:

import { model, alias } from 'modelizr'

const user = model('users')

const employer = alias(user, "employer")
const author = alias(user, "author")

query(
    event(
        book(
            author()
        ),
        employer()
    )
)
alexisvincent commented 8 years ago

You don't loose the ability to apply modifier, however as you say, the expressivity is limited. Model aliases would make the query nice, however you run into issues with namespace conflicts if a field name refers to two different type definitions. Also, it would be painful to alias everything every time. Nested queries such as the one below are common. However. I can't really think of a solution...

Perhaps a more manageable instant fix is allowing pre modifiers. So that you could do something like this.

import { query } from 'modelizr'
import { event, organization, user, book } from './schema.js'

query(
    event(
        organisation.as('company',
            user.as('founder',
                book.as('favoriteBook',
                    user.as('author',
                        company.as('employer',
                            user.as('father')
                        )
                    ),
                    book.as('inspiredBy',
                        user.as('author',
                            user.as('guardian',
                                user.as('mother')
                            )
                        )
                    )
                )
            )
        )
    )
)
alexisvincent commented 8 years ago

Also consider:

query(
    event(
        organisation('company',
            user('founder',
                book('favoriteBook',
                    user('author',
                        company('employer',
                            user('father')
                        )
                    ),
                    book('inspiredBy',
                        user('author',
                            user('guardian',
                                user('mother')
                            )
                        )
                    )
                )
            )
        )
    )
)
alexisvincent commented 8 years ago

And by extension (for query parameters)

query(
    event(
        organisation('company', {id: 5}
            user('founder',
                book('favoriteBook', {limit: 10, offset: 3}
                    user('author',
                        company('employer',
                            user('father')
                        )
                    ),
                    book('inspiredBy',
                        user('author',
                            user('guardian',
                                user('mother')
                            )
                        )
                    )
                )
            )
        )
    )
)
julienvincent commented 8 years ago
query(
    event(
        organisation('company', {id: 5}
            user('founder',
                book('favoriteBook', {limit: 10, offset: 3}
                    user('author',
                        company('employer',
                            user('father')
                        )
                    ),
                    book('inspiredBy',
                        user('author',
                            user('guardian',
                                user('mother')
                            )
                        )
                    )
                )
            )
        )
    )
)

I like this syntax. It fits much better with the current API.

alexisvincent commented 8 years ago

:) Cool Stuffs. This is the best that I can think of at this point in time. Still conflates type system and query but the query language feels closer to graphql.

Something that can also be noted here is that in this example, the model is really only providing a function syntax. Something such as the following would be functionally equivalent.

query(
    event(
        q('company', {id: 5}
            q('founder',
                q('favoriteBook', {limit: 10, offset: 3}
                    q('author',
                        q('employer',
                            q('father')
                        )
                    )
                )
            )
        )
    )
)

if we then made the function q automatically curried we can do the following.


company = q('company')
founder = q('founder')
favoriteBook = q('favoriteBook')
author = q('author')
employer = q('employer')
father = q('father')

query(
    event(
        company({id: 5}
            founder(
                favoriteBook({limit: 10, offset: 3}
                    author(
                        employer(
                            father()
                        )
                    )
                )
            )
        )
    )
)

While on surface this looks the same as your type alias proposal from earlier, it is fundamentally different. As something like company() is really just an alias to a string not an actual type. And so the namespace problem mentioned in the second comment, would not apply.

I'm trying to explore ideas and their implications, I'm not necessarily advocating the above design.

Personally I think the syntax below is the most practical.

query(
    event(
        organisation('company', {id: 5}
            user('founder',
                book('favoriteBook', {limit: 10, offset: 3}
                    user('author',
                        company('employer',
                            user('father')
                        )
                    ),
                    book('inspiredBy',
                        user('author',
                            user('guardian',
                                user('mother')
                            )
                        )
                    )
                )
            )
        )
    )
)
julienvincent commented 8 years ago

Something that can also be noted here is that in this example, the model is really only providing a function syntax. Something such as the following would be functionally equivalent.

Not quite true. It is also providing a schema and defined model relationships. So your following example would not be equivalent.

Your second proposal would require a complete rewrite of the api as models() do not return themselves.

Personally I think the syntax below is the most practical.

Agreed.

julienvincent commented 8 years ago

Your second proposal would require a complete rewrite of the api as models() do not return themselves.

Not quite true I suppose. Could have a type check for a string and return the model as a function.

alexisvincent commented 8 years ago

Not quite true. It is also providing a schema and defined model relationships. So your following example would not be equivalent.

This can be inferred from the root type. You don't need the model schema.

julienvincent commented 8 years ago

Not in the current spec. So at the current state of modelizr, you do need to use specific models.

alexisvincent commented 8 years ago

Not in the current spec. So at the current state of modelizr, you do need to use specific models.

Sure, this was more of an experiment with a query language ;)