type-graphql icon indicating copy to clipboard operation
type-graphql copied to clipboard

Versioning resolvers

Open tbrannam opened this issue 3 years ago • 3 comments

Is your feature request related to a problem? Please describe.

Managing change within a schema over time. Schema evolution is a common pattern that is used. But creates complications and headaches of inventing new names or introducing FieldV2 issues.

Describe the solution you'd like

This blog article has an interesting take on managing schemas by dynamically matching resolvers.

https://blog.logrocket.com/versioning-fields-graphql/

It would be great to see type-graphql have an opinionated approach on schema evolution.

Describe alternatives you've considered

v2 all the things?

Additional context

Naming things is hard, naming them twice is harder.

tbrannam avatar Feb 24 '21 02:02 tbrannam

TypeGraphQL design goals is to avoid strongly opinionated approach, so I don't plan to implement such things directly in the core. However, you can create your own package that will introduce some helpers to make that kind of things easier for other developers.

Also, creating feature request by just pasting a link to an article won't help you bring more attention with it. Please describe the proposed solution in a more descriptive manner, with some API proposal, with describing the current way of using args and directives, etc. Currently I have no idea what do you expect from the "TypeGraphQL support".

Otherwise I will be forced to close this issue for a housekeeping purposes.

MichalLytek avatar Feb 24 '21 21:02 MichalLytek

Is your feature request related to a problem? Please describe.

I am using TypeGraphQL in project in production although it is great, I have problems with versioning / evolving schema and deprecating old fields.

Right now I can only add new fields that have to have not so descriptive names as it's mentioned in issue "Naming things is hard, naming them twice is harder."

So the process of schema evolution looks like mentioned in article:

type Account {
  surname: String!
}
type Account {
  surname: String! @deprecated(reason: "Use `personSurname`")
  personSurname: String
}
type Account {
  personSurname: String
}

And now I don't have really good name for field so if I would like to rename it back to surname I will have to go trough this process again. This results in 4 "evolutions" just to change non-nullable field to nullable. And that's big overhead so I rather end up with not-so-good field name and this over time results in schema with lots of non-so-descriptive fields. And as API grows, maintaining this becomes huge pain.

Describe the solution you'd like

I think this quite big issue so there needs to be some way to solve it. Either implementing some support to core, but you don't like that idea because it is too opinionated, or creating new package as possible add-on for developers using TypeGraphQL. I don't have problem develop or help developing that kind of package, but I am not sure if TypeGraphQL expose enough inner API to make it possible in some developer-friendly way.

Way to version single fields as it is mentioned in article really looks good (or at least I think), but I would probably stick to simple "integer-only" versioning without support of constraints to make it simple to use as possible and because I actually can't imagine advantages of semantic versioning for single fields.

Idea to implement it with current API is enable versioning queries and field resolvers separate to methods:

@Resolver(User)
export class UserResolver {
    @Query(() => UserConnection, { description: 'Gets users' })
    public async users() {
        ...
    }

    @Query(() => UserConnection, { description: 'Gets users', name: 'users', version: 2 })
    public async usersV2() {
        ...
    }

}

Resulting in usage like this:

query {
    users {
        ...
    }
    newUsers: users(version: 2) {
        ...
    }
}

And that same goes for field resolvers:

@Resolver(User)
export class UserResolver {

    @FieldResolver(() => String)
    public async name(@Root() user: User): string {
        ...
    }

    @FieldResolver(() => String, { name: 'name', version: 2 })
    public async nameV2(@Root() user: User): string {
        ...
    }

}

Resulting in usage like this:

query {
    user {
        name
        newName: name(version: 2)
    }
}

Which version would be default if not specified? I would set version 1 as default, but allow developers to change it probably in buildSchema function:

buildSchema({
    resolvers: [...],
    defaultVersion: 3
})

This setting would use version 3 if exists, if not, it would fallback on the next lower one that exists (to 2 and if 2 wouldn't exist, to 1)

Additional context I actually have no knowledge of how TypeGraphQL is built so I don't have clue if this API proposals are even possible right now (either build them into core or to create new package as add-on)

If you have any idea on how to implement this solution without changing core API (using custom/new decorators), please share it :pray:

I would be glad for any opinions on this approach, ideas and etc! :rocket:

martinstepanek avatar Jul 31 '21 02:07 martinstepanek

Is there any update on this?

martinstepanek avatar Apr 12 '22 00:04 martinstepanek