web-dev-qa-db-ja.com

GraphQLの多対多の関係

オブジェクトの配列が2つあるとしましょう。

let movies = [
    { id: '1', title: 'Erin Brockovich'},
    { id: '2', title: 'A Good Year'},
    { id: '3', title: 'A Beautiful Mind'},
    { id: '4', title: 'Gladiator'}
];

let actors = [
    { id: 'a', name: 'Julia Roberts'},
    { id: 'b', name: 'Albert Finney'},
    { id: 'c', name: 'Russell Crowe'}
];

多対多の関係を作りたいと思っています。 Vanilla JavaScriptで始まり、最終的にはGraphQLスキーマで。

JavaScriptでは、次のようなことをしました。

let movies = [
    { id: '1', title: 'Erin Brockovich',  actorId: ['a', 'b'] },
    { id: '2', title: 'A Good Year',      actorId: ['b', 'c'] },
    { id: '3', title: 'A Beautiful Mind', actorId: ['c'] },
    { id: '4', title: 'Gladiator',        actorId: ['c'] }
];
let actors = [
    { id: 'a', name: 'Julia Roberts', movieId: ['1'] },
    { id: 'b', name: 'Albert Finney', movieId: ['1', '2'] },
    { id: 'c', name: 'Russell Crowe', movieId: ['2', '3', '4'] }
];
let actorIds = [];
let movieIds = [];
for (let m = 0; m < movies.length; m ++) {
    for (let i = 0; i < movies[m].actorId.length; i ++) {
        actorIds.Push(movies[m].actorId[i]);
    }
}
for (let a = 0; a < actors.length; a ++) {
    for (let i = 0; i < actors[a].movieId.length; i ++) {
        movieIds.Push(actors[a].movieId[i]);
    }
}
for (let a = 0; a < actors.length; a ++) {
    for (let i = 0; i < actorIds.length; i ++) {
        if ((actors[a].id == 'c') && (actors[a].id == actorIds[i])) {
            for (let j = 0; j < movies.length; j ++) {
                if (movies[j].id == movieIds[i]) {
                    console.log(movies[j].title);
                }
            }
        }
    }
}

Nodeで先行するコードを実行すると、ターミナルは

A Good Year
A Beautiful Mind
Gladiator

これがまさに私が欲しいものです。

残念ながら、GraphQLスキーマで迷子になりました。私がこれまでに持っているのは、もちろんfields関数の内部ですが、次のとおりです。

in_which_movies: {
    type: new GraphQLList(FilmType),
    resolve(parent, args) {

        let actorIds = [];
        let movieIds = [];

        for (let m = 0; m < movies.length; m ++) {
            for (let i = 0; i < movies[m].actorId.length; i ++) {
                actorIds.Push(movies[m].actorId[i]);
            }
        }
        for (let a = 0; a < actors.length; a ++) {
            for (let i = 0; i < actors[a].movieId.length; i ++) {
                movieIds.Push(actors[a].movieId[i]);
            }
        }
        for (var a = 0; a < actors.length; a ++) {
            for (var i = 0; i < actorIds.length; i ++) {
                if ((actors[a].id == parent.id) && (actors[a].id == actorIds[i])) {
                    for (var j = 0; j < movies.length; j ++) {
                        if (movies[j].id == movieIds[i]) {
                            console.log(movies[j].title);
                        }
                    }
                }
            }
        }
        return movies[j].title;
    }
}

GraphiQLで次のクエリを実行すると...

{
    actor(id: "c") {
        name
        in_which_movies {
            title
        }
    }
}

...私はこの応答を持っています:

{
  "errors": [
    {
      "message": "Cannot read property 'title' of undefined",
      "locations": [
        {
          "line": 4,
          "column": 3
        }
      ],
      "path": [
        "actor",
        "in_which_movies"
      ]
    }
  ],
  "data": {
    "actor": {
      "name": "Russell Crowe",
      "in_which_movies": null
    }
  }
}

...これは私にとって奇妙なことですが、cuzターミナルは期待どおりに応答しています

A Good Year
A Beautiful Mind
Gladiator

これまでに作成したすべてのコードは役に立たないと思います。GraphQLで多対多の関係を適切に作成するための新しいガイドラインが必要です。

7
Tylik Stec

TL; DRあなたは物事を考えすぎていると思います。リゾルバーの作業が多すぎるため、コードの推論やデバッグが困難になります。

あなたの質問はGraphQLとはあまり関係がないと思いますが、基礎となるデータに対して適切な操作を行うだけです。私はあなたの例から始めて、GraphQLの観点からそれをステップスルーしようとしますので、あなたが探しているタイプとリゾルバーに行き着きます。

バニラコードから始めます。

_let movies = [
    { id: '1', title: 'Erin Brockovich',  actorId: ['a', 'b'] },
    { id: '2', title: 'A Good Year',      actorId: ['b', 'c'] },
    { id: '3', title: 'A Beautiful Mind', actorId: ['c'] },
    { id: '4', title: 'Gladiator',        actorId: ['c'] }
];
let actors = [
    { id: 'a', name: 'Julia Roberts', movieId: ['1'] },
    { id: 'b', name: 'Albert Finney', movieId: ['1', '2'] },
    { id: 'c', name: 'Russell Crowe', movieId: ['2', '3', '4'] }
];
_

クエリを簡単に行えるように、これをidでインデックス付けされたものに変換することをお勧めします。これにより、通常、本番のGraphQLAPIの背後にあるデータベースまたはKey-Valueストアをより適切にモデル化できます。

これをインデックス付きのものに変換しますが、それでもVanilla JS:

_let movies = {
    '1': { id: '1', title: 'Erin Brockovich',  actorId: ['a', 'b'] },
    '2': { id: '2', title: 'A Good Year',      actorId: ['b', 'c'] },
    '3': { id: '3', title: 'A Beautiful Mind', actorId: ['c'] },
    '4': { id: '4', title: 'Gladiator',        actorId: ['c'] }
};
let actors = {
    'a': { id: 'a', name: 'Julia Roberts', movieId: ['1'] },
    'b': { id: 'b', name: 'Albert Finney', movieId: ['1', '2'] },
    'c': { id: 'c', name: 'Russell Crowe', movieId: ['2', '3', '4'] }
};
_

次に、これらのタイプを表すGraphQLスキーマについて考える必要があります。ここで「多対多」の部分が関係します。サンプルデータからタイプをかなりきれいに導き出すことができると思います。

_type Movie {
  id: ID!
  title: String
  actors: [Actor]
}

type Actor {
  id: ID!
  name: String
  movies: [Movie]
}
_

_[Movie]_はMovieオブジェクトのリストであることに注意してください。基になるデータにID(別名「正規化」、これは私たちが期待するもの)が含まれている場合でも、実際の型付き関係の観点からAPIをモデル化します。

次に、リゾルバーを設定する必要があります。 Actorタイプのリゾルバーを見てみましょう。これが、あなたの例にあるからです。

_movies: {
    type: new GraphQLList(FilmType),
    resolve(parent) {
        // The ids of all the movies this actor is in. "parent" will be the
        // actor data currently being queried
        let movieIds = parent.movieId;
        // We'll build up a list of the actual movie datas to return.
        let actorInMovies = [];
        for (let m = 0; m < movieIds.length; m++) {
            // The m'th movie id.
            let movieId = movieIds[m];
            // The movie data from our indexed "movies" top level object.
            // In production, this might access a database service
            let movie = movies[movieID];
            // Add that movie to the list of movies
            actorInMovies.Push(movie)
        }
        // Then we'll return that list of movie objects.
        return actorInMovies;
    }
}
_

元のリゾルバーでは、おそらく文字列である_movies[j].title_が返され、「List of FilmType」で期待されるものと一致しないことに注意してください。上記の例では、ムービーデータオブジェクトの配列は次のとおりです。戻ってきた。

また、上記のコードはこれを行うための非常に冗長な方法ですが、各ステップについてコメントすることが役立つと思いました。本当に多対多であるためには、Movie型はそのactorsフィールドに対してほぼ同じコードを持っている必要があります。ただし、.map()演算子を使用してこのコードを大幅に簡略化する方法の例を示すために、別の方法で記述します。

_actors: {
    type: new GraphQLList(ActorType),
    resolve(parent) {
        // In this case, "parent" will be the movie data currently being queried.
        // Use "map" to convert a list of actor ids into a list of actor data 
        // objects using the indexed "actors" top level object.
        return parent.actorId.map(id => actors[id]);
    }
}
_
9
Lee Byron