Open19

gqlを自分で記述したくない

podhmopodhmo

思うことをここに箇条書きしてみる

  • gqlを記述したくない
    • 本質的には、何らかのresourceを定義して、そのそれぞれの型のサブセットを返すようなqueryがgraphなのでは?
    • それを記述する方法は別にgraphqlの記法に縛られる必要はない気もする
  • ただし全力でvalidationも補完も効いてほしい
    • DX(開発者体験)の良さは、事前チェックや随時チェックにあるような気がする。
      • ふわっとした記述でparse error (syntax error)とか嫌。
    • つまり現代においてはlanguage serverの対応が必須
      • 既存の言語で記述して、language serverは間借りする (e.g. AWS CDK)
        • あまりきれいにはならない。
      • 自分で言語を作って、language server周りのエコシステムも頑張って整える
        • 勢いがあるうち良いが、メンテナンスが滞ったりしたときに困る

resource

podhmopodhmo

補完を効かせるには型として生成してあげる必要がある。そうすると二段階の作業が必要そう。
objectの定義とquery/mutationの定義はステップが分かれる。

  1. objectの定義 (builder)
  2. 定義からgoのコードを生成
  3. 生成された値を使ってqueryを定義
podhmopodhmo

objectの定義

これは普通に焼き直しになりそう。例えば以下のような記述がほしいとする。

type Todo {
    id: ID!
    text: String!
    done: Boolean! @hasRole(role: OWNER) # only the owner can see if a todo is done
}

普通に記述するだけそう。
:memo: ディレクティブの部分をどうするかは後で考える必要がある。

podhmopodhmo

openapigenを拝借して例えば以下のような感じ。

var b = openapigen.NewBuilder(openapigen.DefaultConfig())

var (
	Todo = b.Object(
		b.Field("id", b.String()), // 正確にはID型
		b.Field("text", b.String()),
		b.Field("done", b.Bool()).Doc("only the owner can see if a todo is done"),
	)
podhmopodhmo

問題はこれだとTodo.Idみたいな形で補完が効かないところ(一旦はすべてexportedフィールドで考えることにする)

podhmopodhmo

resource packageに以下のような値を用意してみる。

package resource

type Field struct {
	Name     string
	Type     string
	Required bool
}

type _TodoDefinition struct { // exportしてしまうと補完候補に出てしまう
	ID   Field
	Text Field
	Done Field
}

var Todo = _TodoDefinition{
	ID:   Field{Name: "id", Type: "ID", Required: true},
	Text: Field{Name: "name", Type: "String", Required: true},
	Done: Field{Name: "done", Type: "Boolean", Required: true},
}
podhmopodhmo

基本的にフィールドは全部unexportedのほうが嬉しいのかもしれない。そうしないと補完の候補に載ってしまいうるさくなる。

Todoはいい感じ。

フィールド名の先の情報は隠れてほしい (e.g. Required)

podhmopodhmo

サブセットがほしい場合ってどういうときなんだろう? inputはサブセットが欲しくなる?

"Passed to createTodo to create a new todo"
input TodoInput {
    "The body text"
    text: String!
    "Is it done already?"
    done: Boolean
}
podhmopodhmo

ネストしたpaginationのようなものを使いたいときには返す値が定義時のオブジェクトをwrappingした型になりそう。

これにargumentsを渡す方法は?

podhmopodhmo

queryの定義

argumentsとかが現れる。どうやって扱うと良いんだろう?

type MyQuery {
    todo(id: ID!): Todo
    lastTodo: Todo
    todos: [Todo!]!
}
podhmopodhmo

goにはキーワード引数などはない。

resource

type _Field struct {
	name string
	typ  Type
}

type typImpl struct {
	name     string
	required bool
}

func (t typImpl) typ()

// primitives
var (
	ID      = typImpl{"ID", true}
	String  = typImpl{"String", true}
	Boolean = typImpl{"Boolean", true}
)

type _TodoDefinition struct { // exportしてしまうと補完候補に出てしまう
	typImpl

	ID   _Field
	Text _Field
	Done _Field
}

var Todo = _TodoDefinition{
	typImpl: typImpl{name: "Todo"},
	ID:      _Field{name: "id", typ: ID},
	Text:    _Field{name: "name", typ: String},
	Done:    _Field{name: "done", typ: Boolean},
}

structの手書きは人間がやるものじゃない。primitiveな型も定義しておいてあげる必要がありそう。

type Query struct {
	Name       string
	Attributes []Attribute
	Return     resource.Type
}

type Attribute struct {
	Name string
	Type resource.Type
}

func use() {
	myQuery := []Query{
		{Name: "todo", Attributes: []Attribute{{Name: "id", Type: resource.ID}}, Return: resource.Todo},
		{Name: "lastTodo", Return: resource.Todo},
		{Name: "todos", Return: resource.Todo}, // TODO: array
	}
	_ = myQuery
}
podhmopodhmo

こんなコードで記述できれば良いんだろうか?

myquery2 = Query(
	Node("todo", resource.Todo).Arg("id", resource.ID),
	Node("lastTodo", resource.Todo)),
	Node("todos", ArrayOf(resource.Todo)),
)
podhmopodhmo

もう少し複雑なネストした型だとどうだろう?
query自体はネストするけど、queryの定義自体はネストしないのだっけ? ↑で言えばTodoがさらにメソッドを持っていたりするときにそれを絞れるみたいな感じなのだっけ?

podhmopodhmo

queryを呼び出すとき

variables経由なのはそれはそうと言う感じ。
https://www.apollographql.com/docs/react/data/operation-best-practices/