graphql-go / graphql

An implementation of GraphQL for Go / Golang
MIT License
9.87k stars 839 forks source link

[Q] how to reuse the fields definition of a object type in another object type? #563

Closed mrdulin closed 4 years ago

mrdulin commented 4 years ago

Here is my senario:

type User struct {
    Loginname string `json:"loginname"`
    AvatarURL string `json:"avatar_url"`
}

type UserDetail struct {
    User
    GithubUsername string        `json:"githubUsername"`
    CreateAt       string        `json:"create_at"`
    Score          int           `json:"score"`
    RecentTopics   []RecentTopic `json:"recent_topics"`
}

var UserType = graphql.NewObject(graphql.ObjectConfig{
    Name:        "User",
    Description: "This respresents an user",
    Fields: graphql.Fields{
        "loginname":  &graphql.Field{Type: graphql.String},
        "avatar_url": &graphql.Field{Type: graphql.String},
    },
})

var UserDetailType = graphql.NewObject(graphql.ObjectConfig{
    Name:        "UserDetail",
    Description: "This respresents an user detail",
    Fields: graphql.Fields{
        // TODO: reuse fields definition of UserType here
        "loginname":  &graphql.Field{Type: graphql.String},
        "avatar_url": &graphql.Field{Type: graphql.String},

        "githubUsername": &graphql.Field{Type: graphql.String},
        "create_at":      &graphql.Field{Type: graphql.String},
        "score":          &graphql.Field{Type: graphql.Int},
    },
})

As you can see, the loginname and avatar_url fields are same in UserType and UserDetailType. I want to extract this common part and reuse it in both UserType and UserDetailType.

We can use embed struct in golang like the User and UserDetail structs. I'd like to do the same thing for graphql-go. It's some kind of composition.

mrdulin commented 4 years ago

Here is a solution, just create base fields and merge base fields with extra fields. A bit like fragment on the client side

schema/user.go:

var UserBaseFields = graphql.Fields{
    "loginname":  &graphql.Field{Type: graphql.String},
    "avatar_url": &graphql.Field{Type: graphql.String},
}

var UserType = graphql.NewObject(graphql.ObjectConfig{
    Name:        "User",
    Description: "This respresents an user",
    Fields:      UserBaseFields,
})

var UserDetailType = graphql.NewObject(graphql.ObjectConfig{
    Name:        "UserDetail",
    Description: "This respresents an user detail",
    Fields: utils.MergeGraphqlFields(UserBaseFields, graphql.Fields{
        "githubUsername": &graphql.Field{Type: graphql.String},
        "create_at":      &graphql.Field{Type: graphql.String},
        "score":          &graphql.Field{Type: graphql.Int},
        "recent_topics":  &graphql.Field{Type: graphql.NewList(RecentTopicType)},
    }),
})

utils.go:

package utils

// MergeMap merge two maps without mutating anyone
func MergeGraphqlFields(a map[string]*graphql.Field, b map[string]*graphql.Field) graphql.Fields {
    m := map[string]*graphql.Field{}
        // TODO: use goroutine to merge concurrencily.
    for k, v := range b {
        m[k] = v
    }
    for k, v := range a {
        m[k] = v
    }
    return m
}