Powered by AppSignal & Oban Pro
Would you like to see your link here? Contact us

Graphql Agent for momenti_web

graphql-agent-for-momenti_web.livemd

Graphql Agent for momenti_web

Target Node

world_list = :net_adm.world_list([:"127.0.0.1"])
:net_adm.world()
target_node =
  world_list
  |> Enum.filter(fn node ->
    node |> to_string |> String.split("@") |> List.first() == "api"
  end)
  |> List.first()

Module

아래의 코드가 가능하다. 라이브북 노드에는 없는 Algae.Either.Righttarget_node 로 부터 로드해와서 Graphql.Agent 를 컴파일하는데 사용할 수 있다. Mix.install([:algae]) 가 계속 실패해서 실망했었는데, 이런 미친 방밥이 가능하다니 😎

{module, binary, filename} = :rpc.call(target_node, :code, :get_object_code, [Algae.Either.Right])
:code.load_binary(module, filename, binary)
{:module, module_name, module_binary, _opt} =
  defmodule Graphql.Agent do
    use GenServer

    alias MomentiEcto.V4.Accounts

    def start(args, name \\ :agent_1) do
      GenServer.start(__MODULE__, args, name: name)
    end

    def query(name, query, variables \\ %{}) do
      GenServer.call(name, {:query, query, variables})
    end

    def mutate(name, query, variables \\ %{}) do
      GenServer.call(name, {:mutate, query, variables})
    end

    def subscribe(name, query, variables \\ %{}) do
      GenServer.call(name, {:subscribe, query, variables})
    end

    # callbacks
    def init({client_pid, access_token}) do
      {:ok, %{client_pid: client_pid, access_token: access_token, valid: false, context: nil},
       {:continue, :validate_token}}
    end

    def handle_continue(:validate_token, %{access_token: access_token} = state) do
      case Accounts.authenticate(:partner, access_token) do
        %Algae.Either.Right{right: partner} ->
          {:noreply, %{state | valid: true, context: %{:partner => partner}}}

        _ ->
          {:noreply, %{state | valid: false}}
      end
    end

    def handle_call({op, query, variables}, _from, %{context: context} = state)
        when op in [:query, :mutate] do
      result = Absinthe.run(query, MomentiWeb.W4.Schema, context: context, variables: variables)
      {:reply, result, state}
    end

    def handle_call({:subscribe, query, variables}, _from, %{context: context} = state) do
      topic = Absinthe.run(query, MomentiWeb.W4.Schema, context: context, variables: variables)
      {:reply, topic, state}
    end

    # handle subscription message
  end

Load Module on remote node

livebook 의 erl_dist.ex 에 사용된 코드를 활용한다.

  defp load_required_modules(node) do
    for module <- @required_modules do
      {_module, binary, filename} = :code.get_object_code(module)
      {:module, _} = :rpc.call(node, :code, :load_binary, [module, filename, binary])
    end
  end

다만, 나는 livebook 에 선언된 모듈을 로드하기 때문에 :code.get_object_code 가 필요없고, load_binary 에서 두번째 인자 filename 은 무의미하기 때문에 타입만 맞춰 호출한다.

:rpc.call(target_node, :code, :load_binary, [module_name, '/graphql_agent.ex', module_binary])
token =
  "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IkhKazlrTFpCdWpXMTl2V3JLckttdSJ9.eyJodHRwczovL21vbWVudGkudHYvIjp7Im9yZ2FuaXphdGlvbiI6eyJkaXNwbGF5X25hbWUiOiJNb21lbnRpIiwiaWQiOiJvcmdfdEhWMDZEZVVrU3BOc0FqVCIsIm5hbWUiOiJtb21lbnRpIn0sInJvbGVzIjpbImFkbWluIiwicGFydG5lciIsImNyZWF0b3IiLCJhbmFseXN0Il0sInVzZXIiOnsiZW1haWwiOiJicmV0QG1vbWVudGkudHYiLCJuaWNrbmFtZSI6ImJyZXQifX0sImlzcyI6Imh0dHBzOi8vbW9tZW50aS1kZXYudXMuYXV0aDAuY29tLyIsInN1YiI6Imdvb2dsZS1vYXV0aDJ8MTAxNDM0MzE0MjI0NDUzMDQyNzEwIiwiYXVkIjpbImh0dHBzOi8vbWFrZXIuYXBpIiwiaHR0cHM6Ly9tb21lbnRpLWRldi51cy5hdXRoMC5jb20vdXNlcmluZm8iXSwiaWF0IjoxNjI4NjcwOTU2LCJleHAiOjE2Mjg3NTczNTYsImF6cCI6InduN0o2Z21JYkZIbGpmczR6emRmWW5Mc3hsNnBVb1FVIiwic2NvcGUiOiJvcGVuaWQgcHJvZmlsZSBlbWFpbCIsIm9yZ19pZCI6Im9yZ190SFYwNkRlVWtTcE5zQWpUIn0.qsZ-iQcE2VCcqNZIM448ubqCXoIstL_YYKlWZVJCRk5oRU3IYcznbX282epvSu2q_9sFBLN7BxdjPyh89-HrxxE4cAgJKKo6tvqNA-A1mzav8u2oHQGnjxFpAPA92o3Wrtvx7lkJeKxCrhk6Ny38QK0cB0jkAS6yc-OQqJSZk0xQTSXF2SqjZavlV5nb67yLqJH8Gh5m5HouirA6mPpjnFFJWkvygbz_DXWo4Dp21YP_3eBuSTPjigbX-AAMGtIW4n1CtXCwSyJloHv0reorpJgQAOG8yRwNSTjeTvPQ1wTVIfgUdS840sipBIrHVEwbAXzVNrRKXR14iwVT3IPpnQ"
{:ok, pid} = :rpc.call(target_node, module_name, :start, [{"pid", token}, :agent_1])
:rpc.call(target_node, :sys, :get_state, [:agent_1])

run graphql

pid
query = """
query {
  me {
    partner {
      roles
    }
  }
}
"""

Graphql.Agent.query(pid, query)

Kill agent

:rpc.call(target_node, Process, :whereis, [:agent_1])
|> Process.exit(:kill)