mmag

ハマったことメモなど

GenStageのcastやcallもdispatchするんですね

github.com

以前ふむふむ眺めただけだったので触ってみたら微ハマりした。

defmodule Producer do
  use GenStage

  ...

  def enqueue(item) do
    GenStage.cast(__MODULE__, {:enqueue, item})
  end

  def handle_cast({:enqueue, item}, state) do
    {:noreply, [item], state}
  end
end

こういうとき、Producer.enqueue(:something)するとconsumerへ1つイベントが流れていく。

こうやって見ると普通だけど、consumerへイベントを流す指示ができるのはhandle_demandだと思いこんでいて、最初は↓の実装をしてた。

defmodule Producer do
  use GenStage

  ...

  def enqueue(item) do
    GenStage.cast(__MODULE__, {:enqueue, item})
  end

  def handle_cast({:enqueue, item}, queue) do
    {:noreply, [], :queue.in(item, queue)}
  end

  def handle_demand(demand, queue) do
    case :queue.out(queue) do
      {:empty, queue} ->
        {:noreply, [], queue}
      {{:value, item}, queue} ->
        {:noreply, [item], queue}
    end
end

EctoからSQL関連の機能が分離されるらしい

github.com

EctoはポスグレなんかのRDBMSと使われることが多いと思われるけど、役割としてはschemaでデータの構造を定義したりchangesetでデータを操作したりrepoを通して実際にデータを保存したりといろいろあり、SQLを扱うことだけが仕事じゃない。どんな風に保存するかはAdapterとかへ既に分離されているのだけれど、もうパッケージ分けちゃって、よりコアな部分をectoとしよう、という話。

予定としてはv3.0からで、利用者は{:ecto_sql, "~> 3.0"}mix.exsに書く。こいつがectoに依存しているので{:ecto, "~> 3.0"}は書かなくていい。もちろんchangesetとかでバリデーションだけしたいんですよという場合はectoを使えばいい。

Apolloのcache updateがやや辛そうに見える

使っているのはvue-apolloだけど。

Mutationした結果をつかってquery cacheを更新する、ということができる。これによってもう一回queryしなくてよくなるなどの効果がある。Optimistic responseと組み合わせると、迅速なフィードバックを利用者に返すことができて体験が良くなる効果も見込める。よくあるコード例は

import createCommentMutation from 'create-comment.gql'
import commentsQuery from 'comments.gql'

const newComment = { ... }

this.$apollo.mutate({
  mutation: createCommentMutation,
  variables: {
    body: newComment.body,
  },
  update: (store, { data: { createComment } }) => {
    const data = store.readQuery({ query: commentsQuery })
  
    data.comments.push(newComment)

    store.writeQuery({ query: commentsQuery, data })
  },
})

なんてやつ。store.readQueryに更新したいキャッシュのクエリを与えてキャッシュの中身を取り出し、mutationの結果を使って編集して、最後にwriteQueryでキャッシュを更新。どこか画面などに反映される。

ただ現実のアプリケーションはこんなに単純なものでもないと思っていて、例えば更新したいクエリというものがvariablesを使っていたとすると、

import createCommentMutation from 'create-comment.gql'
import commentsQuery from 'comments.gql'

const newComment = { ... }
const date = '...'

this.$apollo.mutate({
  mutation: createCommentMutation,
  variables: {
    body: newComment.body,
  },
  update: (store, { data: { createComment } }) => {
    const data = store.readQuery({
      query: commentsQuery,
      variables: { date },  // <- これ
    })
  
    data.comments.push(newComment)

    store.writeQuery({ query: commentsQuery, data })
  },
})

クエリキャッシュを取得するときもvariablesが必要になる。ということは、mutationする関数などに、mutationに必要なパラメータに加えて、どのクエリキャッシュを更新すべきか特定できるだけのパラメータを渡さないといけない。もしかすると大抵の場合はmutationに必要なパラメータから導出できるのかもしれないけど、そうでないならなんかあっちこっちからデータ引き回しそう。Vuexとかでカバーできるのか、設計が良ければそうはならないのか、はて。