ããã«ã¡ã¯ï¼LayerXã® mosa_siru (æ¦æ¬) ã§ãã
LayerX ã¤ã³ãã¤ã¹ã§ã¯ããã¨ã㨠github.com/go-swagger/go-swagger ãå©ç¨ãã¦REST APIãéçºãã¦ãã¾ããããæè¿éçºããã¯ã¼ã¯ããã¼æ©è½ ã®ã³ã³ãã¼ãã³ãã§ã¯GraphQLãåãå ¥ãã¾ããã
GraphQLã«ã¯æ§ã ãªã¡ãªããããããRESTã¨ã®æ¯è¼è¨äºã¯å¤ãããã¾ããããªãåãã¯ç§»è¡ããã®ãããã®çµæã©ããªã£ãã®ããç´¹ä»ãã¦ããã¾ãã
GraphQLã®ã¡ãªãã
GraphQLã®ã¡ãªããã¯ãæ§ã ãªç®æã§èªããã¦ãã¾ããä¾ãã°ãã®è¨äºã«ããã°ã
- å¼·åã«åä»ããããã¹ãã¼ãã§ãããã¨
- ã¢ã³ãã¼ãã§ããã¨ãªã¼ãã¼ãã§ããããªããã¨ï¼å¾è¿°ï¼
- Apollo, Relayãªã©ã®ãã¯ã©ã¤ã¢ã³ãã©ã¤ãã©ãªã«ãããããã³ãã¨ã³ãéçºãè¿ éã«ãªããã¨
- è¤æ°ã®GraphQL APIããã®çµ±åãå¯è½
- å¼·åãªã¨ã³ã·ã¹ãã
ãããããã¦ãã¾ãã
å¼·åã«åä»ããããã¹ãã¼ãã«ãããããã¯ã¨ã³ãã»ããã³ãã¨ã³ãã®ã©ã¡ããåã¨ã³ã¼ããçæãããã¨ãã§ãã¾ããã³ã³ãã¤ã«æã«ãã§ãã¯ã§ãã¾ãããèªåè£å®ãã¢ãã¯ãAPIããã¥ã¡ã³ãã®èªåçæãdirectiveã«ããvalidationã»permission仿§ã®æç¢ºåâ¦ãªã©æ§ã ãªæ©æµãåãããã¨ãã§ãã¾ãã
å®éã«ããã³ãã¨ã³ãã§ã¯ãç¾å¨TypeScriptã§Vue Apolloãå©ç¨ãã¦ãã¾ãããå½ç¶ã®ããã«ã¹ãã¼ãé§åéçºãã§ãã¾ããã¢ãã«ã¯çæãããAPIã¤ã³ã¿ã¼ãã§ã¼ã¹ã®åãçæãã¦ããã¾ãã
ApolloãRelayã¯å¼·åãªãã£ãã·ã¥æ©æ§ãæã£ã¦ãããstoreã«ç¶æ ãæã¤è¨è¨ã«ã¯ã¨ã¦ãç¸æ§ãè¯ãã¨æããã¾ããvvakameãããsonatardãããç±ãæããå æ¬æ¨GraphQL #3ã§èªã£ã¦ãã¾ãã(åãåå ãã¦ãããååã§ã¯ãã®è¨äºã®å 容ã«ã¤ãã¦è©±ãã¦ããã¾ãï¼)
ã¨è²ã ããã¾ãããä»åã®è¨äºã§åãæ³¨ç®ãããã®ã¯ãããã¯ã¨ã³ãã®ã¡ãªããã§ããGo ã® gqlgen ãåæã«èªãã¾ãããããããæ±ç¨çãªãã®ã ã¨æãã¾ãã
ã¢ã³ãã¼ãã§ããåé¡
ãã¡ãã¯è¶ éè¦ã§ãã
è¦ããã«ãã¯ã©ã¤ã¢ã³ããã»ãããã¼ã¿æ§é ããã¾ãæä¾ã§ããã以ä¸ã®çµæ«ãè¿ãããã¨ã§ãã
- ãªã¯ã¨ã¹ãã®éãç¡é§ã«ããããã«ãªã£ã¦ãã¾ã(N+1ãªã¯ã¨ã¹ãåé¡)
- ãªã¹ãé¢ã®ã¬ã¹ãã³ã¹ã«ã»ãããã¼ã¿ãå ¥ããã£ã¦ããªããããåãªã½ã¼ã¹ãåãã«ãããã¿ã¼ã³
- ãããé¿ããããã«ãREST APIã®ä»æ§ããã¯ã©ã¤ã¢ã³ãUIã®é½åã«å¼ããããã¦ãã¾ã
GraphQLã§ã¯æè»ãªãã¼ã¿æ§é ã§ã¬ã¹ãã³ã¹ãæ§ç¯ãããã¨ãã§ããããã大ä½ã®ã¦ã¼ã¹ã±ã¼ã¹ã§ã¯1ã¤ã®ãªã¯ã¨ã¹ãã§å ¨ã¦è¿ããã¨ãã§ãã¾ãã
é©ãã¹ããã¨ã«ããã®ããã®ããã¯ã¨ã³ãã®å®è£ ãã·ã³ãã«ã§ããæè¦çã«ã¯1ã¤1ã¤ã®ãªã½ã¼ã¹ï¼ã®Resolverï¼ãç¡å¢ã«å®è£ ããã°ããã®è¦æ±ãæºãããã¨ãã§ãã¾ãã
ãªã¼ãã¼ãã§ããåé¡
GraphQLã§ã¯ã»ãããã¼ã¿ãã¯ã©ã¤ã¢ã³ãå´ãæç¤ºçã«è¦æ±ãããããããªã¼ãã¼ãã§ãã(ä¸è¦ãªãã¼ã¿ã®åããã)ãããªããªãããµã¼ãã¼ãµã¤ãã®ããã©ã¼ãã³ã¹ãåä¸ãã帯åãç¯ç´ããã¾ãã
RESTã§ãããããªã®ã¯ãå®éã«ä½¿ããã¨ã®ãªããã£ã¼ã«ããè¿ãã¦ãã¾ããã¨ã§ãã
ç¹ã«ã ãªã¬ã¼ã·ã§ã³ããããã¼ã¿ãç¡é§ã«æ§ç¯ãã¦ãã¾ããã¨ãã¨ã¦ãæçãã§ããä¾ãã°ã¤ã¡ã¼ã¸ã§ããã以ä¸ã®ãã㪠GET /usersã¬ã¹ãã³ã¹ã¯ãã¦ã¼ã¶ã¼ã°ã«ã¼ããè¿ãã¦ããã¾ãã
{ "users": [ { "id": "1", "name": "mosa", "group": { "id": "111", "name": "LayerX" } }, { "id": "2", "name": "ymatsu", "group": { "id": "222", "name": "LayerY" } } ] }
LayerX ã¤ã³ãã¤ã¹ã§ã¯RDBã使ã£ã¦ããã®ã§ããããªã¹ãé¢ã§ã¯ä½¿ãå¿
è¦ããªãã®ã« group ãæ§ç¯ãã¦ãããç¡é§ã¯ã¨ãªãçºçãã¦ãã¾ãåå ã¨ãªã£ã¦ãã¾ããã
GraphQLã§ã¯ã以ä¸ã®ãããªå¿
è¦æå°éã®ã¯ã¨ãªã«ããã°ãè£å´ã§ groups ã®ãã¼ã¿ã«å
¨ã触ããã¨ãªãã¬ã¹ãã³ã¹ãæ§ç¯ãããã¨ãã§ãã¾ããï¼ããã¦ä¾ãã°Goã ã¨ãgqlgenãèªç¶ã«ä½¿ãã°ãä½ãèããã¨ããããã£ãæ¯ãèãã«ãªãã®ã§ãï¼
{ users { id name }
{ "data": { "users": [ { "id": "1", "name": "mosa" }, { "id": "2", "name": "ymatsu" } ] } }
ããã¯ã¨ã³ãã³ã¼ãã®è¦éãã®æªãã®è§£æ±º
ä»åã®è¨äºã§æãåã強調ããã (ããã¦ä¼ãããããªãæªãã) ã®ããã¡ãã§ãã
éå»ã®å®è£
Goã§ä¸è¨ã®ãã㪠GET /users ã¬ã¹ãã³ã¹ãæ§ç¯ããããã«ã¯ãããã¾ã§ä»¥ä¸ã®ãããªstructã®embeddedãå®ç¾©ãã¦ã
type User struct { ID string `json:"id"` Name string `json:"name"` GroupID string `json:"group_id"` } type Group struct { ID string `json:"id"` Name string `json:"name"` } type UserEmbedded struct { User Group }
以ä¸ã®ãããªã¡ã½ããã§å é¨çã«åå¾ããåã¬ã¤ã¤ã¼ã§å¼ãåãã¦ãã¾ããã
func GetUserEmbedded(ctx context.Context) ([]*UserEmbedded, error)
- â»å®éã¯
UserEmbeddedã«ã¯ããããªãã®ãEmbedããã¦ãã¾ãã - â»åèã§ãããLayerX ã¤ã³ãã¤ã¹ã§ã¯ xo ãå©ç¨ãã¦ãããusersãã¼ãã«ã¨groupsãã¼ãã«ããèªåã§User, Groupã® struct ãçæããã¾ãã LayerX インボイスの技術スタック〜分野横断で開発するためのSchema Driven Development〜 - LayerX エンジニアブログ
- â»ãã¼ã¸ã³ã°ç³»ã¯çç¥
ãã¦ããã®ã¡ã½ããã®ä¸ã¯ãå®éã«ã¯æ²ãããã¨ã«ãªã£ã¦ãã¾ãã
æ°ã®å©ããORMãããã°å¥ãªã®ã§ããããã
- users DBããã
var users []*Userãåå¾ (Joinã¯ããªã) - N+1ãé¿ãããããusersããgroup_idãã²ã£ãã¬ããDBãã
var Groups []*Groupãåå¾ - ãããããã
[]*UserEmbeddedãæ§ç¯
â¦ã®æµããï¼N+1ãé¿ãã¦å®éã«éã«ããã¦ã¿ãã®ã以ä¸ã§ããèªã¾ãªãã¦ããã§ããã³ã¬ã¯ã·ã§ã³æä½ããã¤ã¼ããªã®ã§ãè¾ããã¨ããããã°ããã§ãï¼
func GetUserEmbedded(ctx context.Context) ([]*model.UserEmbedded, error) { // 1. usersåå¾ users, err := GetUsersFromDB(ctx) if err != nil { return nil, err } // 2. groups åå¾(N+1ãé¿ããã) groupIDSet := map[string]struct{}{} for _, user := range users { groupIDSet[user.GroupID] = struct{}{} } groupIDs := []string{} for id, _ := range groupIDSet { groupIDs = append(groupIDs, id) } groups, err := GetGroupsByIDsFromDB(ctx, groupIDs) if err != nil { return nil, err } // 3. UserEmbeddedæ§ç¯ id2Group := map[string]*model.Group{} for _, group := range groups { id2Group[group.ID] = group } res := make([]*model.UserEmbedded, len(users)) for i, user := range users { group, ok := id2Group[user.GroupID] if !ok { return nil, errors.New("group not found") } res[i] = &model.UserEmbedded{ User: *user, Group: *group, } } return res, nil } func GetUsersFromDB(ctx context.Context) ([]*model.User, error) { return []*model.User{}, nil //ç¥ } func GetGroupsByIDsFromDB(ctx context.Context, ids []string) ([]*model.Group, error) { return []*model.Group{}, nil //ç¥ }
ä½ããããããæ°ããã¾ãããä»ã¾ã§ãããªå®è£ ããã¦ãã¾ãããï¼ãããhandler層ã¯çç¥ãã¦ãã¾ãããï¼
gqlgenãå©ç¨ããå®è£
ãã¦ããããgqlgenã使ãã¨ã©ããªãã§ããããã
çµè«ããè¨ãã¨ã以ä¸ã®ã¡ã½ãããå®è£ ããã ãã§ãçµããã§ãããã£ï¼
func GetUsersFromDB(ctx context.Context) ([]*model.User, error) { return []*model.User{}, nil // ç¥ } func GetGroupByIDFromDB(ctx context.Context, id string) (*model.Group, error) { return &model.Group{}, nil // ç¥ }
ä¸è¨ã¡ã½ãããåresolverãå¼ã³åºãã³ã¼ããç½®ãã¦ããã¨ãgqlgenã¯æçµçãªã¬ã¹ãã³ã¹ãæ§ç¯ãã¦ããã¾ãããã£ï¼éå»ã®å®è£ ã§è¨ãhanlderã®çç¥ãã¦ãªããã§ããããã
func (r *queryResolver) Users(ctx context.Context) ([]*model.User, error) { return GetUsersFromDB(ctx) } func (r *userResolver) Group(ctx context.Context, obj *model.User) (*model.Group, error) { return GetGroupByIDFromDB(ctx, obj.GroupID) }
å®éã®åãã¨ãã¦ã¯ãã¾ãqueryResolver(éã«è¨ãhandler)ã§ []*model.User ãåå¾ãã¾ããåUserã® GroupID ãã£ã¼ã«ããã¡ããã¨ãã *model.Group ã«ãã userResolver ããgqlgen ã®èªåçæã³ã¼ãããããªã«å¼ãã§ãããã®ã§ããï¼ãããResolverã®åã¯èªåçæãããã®ã§ãä¸ãåããã ãã§ããï¼
group ãè¦æ±ããªãã¯ã¨ãªã®å ´åããã®resolverã¯å¼ã°ããªãããããªã¼ãã¼ãã§ãããããã¨ãããã¾ããã
...ããã¾ãä¼ãããªãæ°ããã¾ãããã¨ã«ããæ¥½ãªã®ã§ããããã°ããã¯ä½é¨ããã®ãä¸çªè¯ãã®ã§ããã²å ¬å¼ãã¥ã¼ããªã¢ã«ã®TODOãªã¹ãããã£ã¦ã¿ã¦ãã ããã¾ãã
ããã«ããã³ã¼ãã®è¦éãããããªããããããªãã®ãEmbeddedãã¦ããå°çããè§£æ¾ãããããã©ã¼ãã³ã¹ãåä¸ãã¾ããï¼
ã¢ã³ãã¼ãã§ããåé¡ã¨åãããã¨ãæ¬å½ã«ãåãªã½ã¼ã¹ãã«æ³¨ç®ãã¦serveããã°ããã¨ã¯ãããªã«ä½¿ããããã¨ããæè¦ãå¾ã¾ããã
ããN+1ãããï¼
ãã£ããã¬ã¾ããã GetGroupByIDFromDB ã«ãã£ã¦æ¯åResolveããã®ã¯ãN+1ãªãã§ãã
SELECT * FROM groups WHERE id = '111'; SELECT * FROM groups WHERE id = '222';
ããã解決ããã®ãdataloaderã¨ããä»çµã¿ã§ããdataloaden ãæ¤è¨ãã¾ããããç¾å¨ã¯ github.com/graph-gophers/dataloader ã使ã£ã¦ãã¾ãã
ããé·ããªã£ã¦ãã¾ã£ãã®ã§èª¬æãã¯ãããã¾ããï¼ãã¡ãã®ã©ã¤ãã©ãªã¯ãgroup_id ããã®åå¾ãä¸å®æéãããã¡ãªã³ã°ãã¦ã䏿°ã«åå¾ãããã£ã¦ãããããå®è£ ãããããããã¦ããã¾ãã
SELECT * FROM groups WHERE id in ('111', '222');
å®éã®ä½¿ãæ¹ã¯ä»¥ä¸ã®è¨äºã¨ãµã³ãã«ãåèã«ãã¾ãããæè¬ã
https://user-first.ikyu.co.jp/entry/go-graphql-dataloader
https://github.com/hatena/go-Intern-Bookmark
ã¾ã¨ã
- GraphQLã«ã¯ããããªã¡ãªãããããã
- ãã®ä¸ã§ãããã¯ã¨ã³ãã«æ³¨ç®ããã¨ããªã¼ãã¼ãã§ããã»ã¢ã³ãã¼ãã§ããåé¡ã解決ãã¤ã¤ãã³ã¼ãã®è¦éããã¨ã¦ããããªãã
- dataloaderã¯åã
ã¨ãããããã§ãã
ã¾ã ã¾ã ç´¹ä»ãããã¦ããªã話ãããã¾ãããä»ã®æã¯GraphQLã®é¸å®ã¯æ£è§£ã ã£ããªã¼ã¨ãããæ°æã¡ã§ãæå¿ããæããªããéçºãé²ãã¦ããã¾ãã
æå¾ã«ãªãã¾ãããLayerXã¯çµ¶è³ã¨ã³ã¸ãã¢æ¡ç¨ä¸ã§ãã®ã§ãå°ãã§ãèå³ããã¾ãããä¸åº¦è©±ãèãã«æ¥ã¦ãã ããï¼
