Elixirで遠隔PCに侵入#2風「GenServerでサーバ／クライアントを気軽に作る」

2ヶ月ぶりのご無沙汰です:bow_tone1:

前回の [fukuoka.ex#1](https://techjin.connpass.com/event/56497/) の後くらいから、福岡と東京を行ったり来たりしつつ、以下登壇／勉強会をこなし、最新技術をプロダクション採用している種々企業のスーパーエンジニアの方々にお話を聞きに行く、というハードな2ヶ月で、このElixirコラムもすっかり止まってしまいました…:sweat:

- [ChibiDeveloperさんのLT会](https://cdg.connpass.com/event/57050/)
- [データサイエンスLT](https://datascience.connpass.com/event/56428/)
- [CyberAgentさんのデータ分析勉強会](https://cyberagent.connpass.com/event/58808/)
- [RustのLT会](https://rust.connpass.com/event/56276/)
- [ファンクション倶楽部](https://esminc.connpass.com/event/57534/)
- [IoTLT](https://iotlt.connpass.com/event/56277/)
- [Fukuoka CTO meetup](https://connpass.com/event/59592/)
- [Scala福岡2017](https://scala.connpass.com/event/57941/)
- [「福岡ｘ人工知能…」第1回イベント](https://techjin.connpass.com/event/59760/)

その一方で、こうした活動の中で出会った方々と、Elixirの良さを共有する機会もあり、次に覚えるプログラミング言語としてElixirを多くの方に選んでいただけた、良質な布教活動（笑）の2ヶ月でもありました :wine_glass:

少しでもElixirがプロダクション採用されるよう、より本番運用やデータ分析みたいな領域にアプローチする内容にてリスタートしたいと思いますので、どうぞよろしく :blush:

さて今回の連載は、[Elixirで遠隔PCに侵入ハッカー気分](http://qiita.com/piacere/items/96398166f72fc2d921e9) の続きをやると見せかけ、途中でOTPを使ったリファクタリングをしたり、「耐障害性」に分岐するようなパラレルワールド的展開をしてみようかな、と :ocean:

[![image.png](https://qiita-image-store.s3.amazonaws.com/0/155423/e570984a-7c6d-d316-df08-159e75bb9e2b.png)](https://www.slideshare.net/piacere_ex/elixir2pc-75571538)

# 前準備

スライドP6にある通り、mixで「Pass」というElixirプロジェクトを作成済みのところからスタートします

iexを起動しておいてください

```
# iex -S mix
iex>
```

また、スライドでは、PC間での通信をする例としていますが、説明を分かりやすくするため、サーバーもクライアントも1台のPC内で行うこととします

# サーバとクライアントをGenServerで1つにまとめる

P25で定義している「pwdサーバ」は、以下の通り、File.cwd()でカレントフォルダを取得して返すだけのサーバーです

```elixir:lib/pass.ex
defmodule Pass do
　…
	def start_pwd_server() do
		pid = spawn( Pass, :pwd_server, [] )
		:global.register_name( :pwd, pid )
	end
	def pwd_server() do
		receive do
			{ sender_pid, _ } -> 
				{ :ok, result } = File.cwd()
				send( sender_pid, { true, result } )
		end
		pwd_server()
	end
　…
```

P28で定義している「pwdクライアント」は、以下の通り、pwdサーバプロセスにメッセージを投げて、フォルダパスを待ち受けるだけのクライアントです

```elixir:lib/pass.ex
defmodule Pass do
　…
	def pwd() do
		send( :global.whereis_name( :pwd ), { self(), "" } )
		listen()
	end
　…
```

まず、pwdサーバを起動します

```elixir

iex> Pass.start_pwd_server
```

このサーバに問合せをすると、カレントフォルダが取得できます

```elixir
iex> Pass.pwd
/code/pass
```

こういったサーバ／クライアントのアプリは、機能の違いはあれど、構成やインタフェースはほぼ同じため、Elixirでは、「GenServer」という名前で汎用モジュール化されています :aerial_tramway:

以下のように「use GenServer」をモジュール内に記述し、handle_call()でサーバ側処理を書けば終わりです

```elixir:lib/pass_genserver.ex
defmodule PassGenServer do
	use GenServer

	def handle_call( :pwd, _from, state ) do
		{ :ok, result } = File.cwd()
		{ :reply, result, state }
	end
end
```

GenServerでのpwdを試してみましょう

GenServer.start_link()でサーバ起動し、GenServer.call()でhandle_call()に定義した処理が呼び出せます

```elixir
iex> recompile()
iex> { :ok, pid } = GenServer.start_link( PassGenServer, "" )
{:ok, #PID<0.297.0>}
iex> GenServer.call( pid, :pwd )
"/code/pass"
```

うまくいきました :thumbsup:

# GenServerで書く効果

まず、GenServer版と、そうで無い版のコードを見比べて欲しいのですが、コード量が1/3程度にスリム化されています :dancer_tone1:

次に、GenServer版のコードには、本質的な処理（ここではpwd実行）しか書いておらず、プロセス起動やメッセージパッシングのような処理は一切書かなくて良くなっています

サーバとクライアントを別々に定義する必要が無くなり、1つにまとめられるので、メンテナンス性も向上しています

そして、ここからが凄いのですが、関数名と引数／返却を少し変えるだけで、非同期化対応できてしまうのです！ :flushed:

# GenServerだと非同期化もラクラク

ここまで作ったものは、同期処理のため、スリープを入れると返ってこなくなることを、まず確認しておきます

```elixir:lib/pass_genserver.ex
defmodule PassGenServer do
	use GenServer

	def handle_call( :pwd, _from, state ) do
		{ :ok, result } = File.cwd()
		Process.sleep( 3000 )
		{ :reply, result, state }
	end
end
```

実行すると、処理呼び出し後、スリープ分、待たされます :hourglass_flowing_sand:

```elixir
iex> recompile()
iex> { :ok, pid } = GenServer.start_link( PassGenServer, "" )
{:ok, #PID<0.297.0>}
iex> GenServer.call( pid, :pwd )
　…
　（3秒、待たされる）
　…
"/code/pass"
```

非同期化するには、handle_call() を handle_cast() に変更して、引数_fromを削除し、返却を:noreplyに変える… たったこれだけ :thinking:

```elixir
:lib/pass_genserver.ex
defmodule PassGenServer do
	use GenServer

	def handle_cast( :pwd, state ) do
		{ :ok, result } = File.cwd()
		Process.sleep( 3000 )
		IO.puts "on handle_cast(): " <> result
		{ :noreply, state }
	end
end
```

呼出も、GenServer.cast() に変え、試してみると．．．

```elixir
iex> recompile()
iex> { :ok, pid } = GenServer.start_link( PassGenServer, "" )
{:ok, #PID<0.406.0>}
iex> GenServer.cast( pid, :pwd )
:ok　　　　　　　　　　　　　　　　　 　【←呼出後、即座に返ってくる】
iex> on handle_cast(): /code/pass　　【←3秒後に表示される】
```

バッチリ非同期になりました :laughing:

元々のコードもそんなに複雑では無かったのですが、これはそもそもElixirが、サーバ／クライアントのような、マルチプロセスを書きやすい言語だからな訳ですが、GenServerを使うと、もはや本質的な処理以外は、書かなくて良いレベルまで昇華されます、OTP恐るべし :scream:

（[次回](http://qiita.com/piacere/items/d66a12b3c889ca1d6ab1)に続く）

---

# p.s.

:stars: :stars: <b>【お知らせ】「fukuoka.ex #2」、今月末8/24（金）に開催です</b> :stars: :stars:

ElixirとPhoenix（高速Web・APIフレームワーク）のマルチプロセスと耐障害性をテーマに、セッションを行います

ちょっと難しそうに聞こえるテーマですが、分かりやすく噛み砕いてセッションしますし、懇親会でのご質問もドシドシ受け付けますので、ご安心ください。

https://techjin.connpass.com/event/63493/
[![image.png](https://connpass-tokyo.s3.amazonaws.com/thumbs/cb/13/cb130a03343bc310b07244c308b940db.png)](https://techjin.connpass.com/event/63493/)