Goをやらないとまずいと言われたので触りつつ、適用範囲を考える
まあ、きっかけは流れてきたこれなんですが・・・
・・・突っ込みどころはあるのですが、そこはおまけに回すとして、
重要なのは「Goが伸びている」ということですよね
前から一度くらいGoに触ろうとは思っていたものの、そのきっかけがなかったので、
せっかくなのでGoを軽く書いてみたわけですが・・・
・・・結論からいえば「RailsっぽいものをGoで書こうとするな」ってことですΣ(・ω・ノ)ノ
Goの環境構築(GOPATHの罠)
インストールはMacなら「brew install golang」とかするだけなのですが、
ややはまりどころなのが「GOPATH」の設定です
GOPATHは全てのコードが配置されるベースPATHです
それこそ、自分のコードもライブラリも関係なく「全部」です
Goのimportは全て、GOPATHからの相対PATHとして定義されています
逆に言えば、全てのGo関連のコードに同じPATHが強制されるため、
(gemやyarnのように)バージョン管理をするという概念はありません *1
# .bash_profileや.envrc等 export GOPATH="/path/to/gopath" export PATH="$GOPATH/bin:$PATH"
こんな感じで、GOPATH/binにもPATHを通しておけば、より安心かと(`・ω・´)
Goの仕様
Amazonにちょうどいい本があったので、ポチってみましたが・・・
プログラミング経験者がGo言語を本格的に勉強する前に読むための本
- 作者: 天田士郎
- 発売日: 2017/03/10
- メディア: Kindle版
- この商品を含むブログを見る
・・・1時間もかからずに概要は把握できますΣ(・ω・ノ)ノ
言語仕様に難しい要素はないというより、
意図的に徹底して複雑なパラダイムを排除しているくらいです
プロとして何らかの言語で実績があるプログラマはもちろん
大学で軽くCを触ったことがあるくらいでも、おそらく書けてしまいますし、
まさにそれがGoの重要な「設計思想」だと思います *2
「誰が書いても一定のクオリティのコードが強制される」という意味では、
Pythonに近い思想ですが、圧倒的に簡単です
Pythonのように言語自体がインデントを強制するのではなく、
gofmtというフォーマッタをデフォルトで持っており、
そういった意味でもPythonより柔軟で現実的な仕様だと思います(`・ω・´)
なぜGo言語 (golang) はよい言語なのか・Goでプログラムを書くべき理由
RailsっぽいものをGoで書いてみる
こうなると、何かをGoで実装してみようという話になるのですが、
チェンクロパーティーシミュレーター(以下ccpts)のAPI層をGoで書いてみることに
Get our light! - チェンクロ パーティーシミュレーター
GitHub - parrot-studio/cc-pt-viewer
サンプル的にまずはこれだけ...φ(・ω・`)
どんな言語でも、最近は「Railsっぽいもの」がたいていあるので、
軽く探してみたところ、こちらが見つかりました
https://revel.github.io/ (Railsっぽいもの)
http://doc.gorm.io/ (ActiveRecordっぽいもの)
後者はマイグレーションとか備えていてなかなかですが、
今回は不要なので、接続とmodel定義部分だけ使いました
package models type Arcana struct { Id uint64 `gorm:"primary_key" json:"id"` Name string `sql:"size:100" json:"name"` Title string `sql:"size:100" json:"title"` ArcanaType string `sql:"size:20" json:"arcana_type"` Rarity uint32 `json:"rarity"` Cost uint32 `json:"cost"` // 以下略 }
こんな風に書くだけで、JSONへのシリアライズも定義できるのは楽ですね(`・ω・´) b
JSONを返すController部分も本質的にはこれだけです
func (c Api) Index() revel.Result { arcanas := []models.Arcana{} DB.Limit(20).Order("id desc").Find(&arcanas) // なんとなくActiveRecordっぽくかける return c.RenderJSON(arcanas) // リストを渡すだけでいい感じにJSONにしてくれる }
あとは、revel経由でコンパイル・実行するだけで、APIが動いてしまいますΣ(・ω・ノ)ノ
$ revel run ccptsgo
# http://localhost9000/api にアクセスするとJSONが返る
コンパイルもびっくりするほど速く、「rails s」したときより速いかもしれませんΣ(゚Д゚)ガーン
こうなると、うまいこと関連のmodelとかもJSONで返せるのでは・・・と、
調べようと思ったところで、ふと思ったのです
果たしてこのままccptsをGoで実装することに意味はあるのか・・・とΣ(・ω・ノ)ノ
一本APIができたことで、全体の工程がおぼろげながら把握できるようになったわけですが、
それにより逆に「Railsが暗黙的にやっていることをRevelで実装するコスト」も見えてきまして、
その多大なコストをかけてGoで書いたところで、コストとメリットが釣り合わないよね・・・と*3
Goの使いどころ
そもそも、Goが出てきた経緯を考えれば、
Goは「大規模なコードを書くための言語」ではなく、
「大規模システムを構成する "小さなコンテナ" を書くために特化した軽量言語」なのです
GoogleがMapReduceを発表してから今まで、
大規模システムは「小さな何かが協調して大きなデータを処理する」という形で進化してきました
「小さな何か」はクラスタ*4だったり軽量プロセス*5だったりしましたが、
最近だとOSとアプリが一体化した「軽量コンテナ」になっていて、
そのコンテナの上で動くバイナリを実装する言語としてGoをとらえるとしっくりきます
だからこそ、Goはクロスコンパイラを持っているのです
コンテナのOSはそれ自体が超軽量で特殊な環境であり、
プログラマが使っているOSとは全く異なるはずですからね
先日もDockerに関してちょっと書きました*6が、
マイクロアーキテクチャをベースにした時代にあわせて、
同じようにDockerという「軽量コンテナをデプロイする仕組み」ができたと考えられます
すごく極端な例で言えば、Railsのアーキテクチャをこのように分割するのがコンテナです
(これが正しい設計はどうかはともかく)
# [xxx] それぞれがコンテナ [リクエスト受け付け] IN:テキスト OUT:リクエスト情報テーブル -> [リクエストのparse] IN:リクエスト情報テーブル OUT:リクエスト情報(header/body/etc...) -> [ルーティングの選択] IN:リクエスト情報 OUT:処理先コンテナ名 ->[リクエストの処理] IN:リクエスト情報 OUT:レスポンス情報 -> [パラメータのparse] IN:リクエスト情報 OUT:パラメータテーブル -> [model取得(検索)] IN: リクエスト情報 OUT:modelの配列 -> [データリソースへのアクセス] IN:クエリ OUT:結果 -> [結果をmodelに格納] IN:結果 OUT:modelの配列 -> [結果の構築] IN:modelの配列 OUT:レスポンス情報
「一つのメソッド」が「一つのコンテナ」くらいの粒度で考えれば、
Goがシンプルな構造しか持たないとしても、なんら問題がないことがわかると思います
先ほどのサイトにもこういう問題が挙げられていましたが・・・
・・・そもそも、これが問題にならない粒度の「小さなコード」に使うべきなんだろうなと
「こんな非効率なことは意味がない」と思うかもしれませんが、
(MapReduceが適用されるような)大規模なデータを扱う場合においては、
モノリシックなアプリの常識は通用しません(´-ω-)
逆に言えば、Goを適用するような「軽量コンテナの集合体」の場合、
「どのようなコンテナに分割するのか?」という設計が最重要課題になります
小説ではありますが、こちらに登場する「Vilocony」と、
その上に構築された「KNGSSS」こそ、
(言語がJavaとはいえ)まさにコンテナを用いた大規模システム開発の事例と言えるでしょう
現実でもコンテナの運用はいろいろ難しいらしく、
白川さんの能力と執念がなければカットオーバーは難しかった気はしますが、
わかりやすい事例です
そもそも、Go自体が関数型的な仕様を持っていなかったとしても、
コンテナを用いたシステムの設計そのものに関数型的な思想が必須になるわけで、
関数型的な知識がGoで構築されたシステムに適用できないなんてことはないはずなんですよね(´-ω-)
関数型的な設計でよく出てくるのがUNIXの思想で、
「小さなプログラムをつないで処理」が、「小さなコンテナをつないで処理」になっているだけです
その意味では、UNIXは立派な「マイクロアーキテクチャのシステム」です
まとめ
Goの設計思想を考えれば、「コンテナで構築された大規模システム向け」だとは思うのですが、
今までのモノリシックなシステムで適用できないのか・・・というと、
全くできないってことはないはずです
(私のような)「普通のエンジニアが考える普通のWebサービス」は、
おそらくRuby/PHP/Python等の方が実装工数が小さく、
必要なライブラリが揃っており、リリースまでの工数を小さくできると思います
そういったシステムが肥大化していって、
パフォーマンスが要求された場合に、裏側の一部をGoのコンテナに置き換えていく、
なんて改善計画は十分にありだと思います *7
幸い、Goは前述の通り「言語仕様がめっちゃシンプル」なので、
「何らかの言語を触ったことがあればとりあえず書ける」という利点があります
こちらの記事でも、「普通に書くだけでかなりのパフォーマンスが期待でき、
チューニングの時間を短縮できる」と書いてますが、
一方で、「CURDを扱うだけの普通のAPIはPythonがいい」とも書いてあります
やはり・・・
システムをうまくレイヤー分けして、それぞれの処理が得意な処理系(言語)を選ぶ
・・・というのが大事ですね(´-ω-)
(そしてまたおまけに続く・・・)
*1: ここは批判の対象になっているようですが、後述するように、「本来のGoの適用領域」からするとむしろ、「一つのバイナリ単位で環境を分ける」くらいでもいいのでは・・・と思います なんなら、direnvを使ってプロジェクトディレクトリごとにGOPATHを切り替えれば解決するわけですし
*2: 強いていえば、「構造体」といわれてメモリ空間が意識できるなら、という前提はあるかもしれません まあ、どんな言語であれ、メモリ空間を意識できるかは重要です
*3: なぜ釣り合わないのかについて、一度は長々書いたのですが、どんどん本題からずれていってしまうので、ばっさり切りましたΣ(・ω・ノ)ノ
*6: https://parrot.hatenadiary.jp/entry/2018/07/27/162939
*7: かつてもRubyで書いたシステムのバックエンドをScalaで置き換えるとかありましたよね? わりと有名な「Twitter」ってサービスですがΣ(・ω・ノ)ノ
性能と無関係にUnicornからPumaに移行した件
今回の結論を先に書けば・・・
「CapistranoとPumaをあわせて使うとめっちゃはかどる」
・・・って話でございます
Unicornにこだわりがなければ、Pumaは便利だと思います、以上Σ(・ω・ノ)ノ
(私には必要だが一般的には無視していい)前置きというか経緯
そもそもCapistranoはお仕事で使ったことがありましたが、
チェンクロパーティーシミュレーター(以下、ccpts)ではまだ導入してませんでした
お仕事でごりごり使っていたのはRails4.2とかの頃で、
Passengerは重たいよねって認識*1から、Unicornを当たり前のように使っており、
ccptsでもNginx+Unicornを手動デプロイで運用してました
別に手動でも困ってなかったのですが、一つだけ問題がありまして
サーバダウンから再起動した際、サービスも一緒に起動してほしかったので、
init.dから(ccpts内の)unicornを起動する仕組みにしていたのですが、
実行ユーザーを考えてなかったので、いろいろなファイルがroot権限になってしまいましてΣ(゚Д゚)ガーン *2
それを直すのも面倒なのでそのまま運用していたのですが、違和感があったのも事実で、
そこを修正するついでにデプロイプロセスを改善したいってのもあり、
最初はDockerを検討したわけですよ、Dockerを
最近はいくらでもドキュメントがあるので、DB+Redis+Railsのコンテナを作成し、
サービスを起動するところまではできたわけです
・・・もちろん、ローカルで(´-ω-)
問題は「これをどうやってデプロイするか?」とか、
「修正からデプロイまでどうやって運用するか」なのですが・・・
ccptsのような「普通のサイト」だと、あまりに重すぎなんですよね
たぶん、「Dockerでデプロイする環境」は作れます
ただ、それが「メリットをデメリットが上回った状態」なのかというと、
激しく疑問だったわけです(´・ω・`) *3
とはいえ、「メリットをデメリットが上回った状態」を証明するには、
「もっと低コストなデプロイプロセスで回せる」ことを示す必要があり、
「Capistranoならもっと低コストなはずだよね・・・?」という流れでのCapistranoでございます
CapistranoでPumaだと、Unironより何がいいのか?
前置きが長かったので、結論から先に...φ(・ω・`)
- unicornと違い、Rails5.xでは標準でpumaがついてくる(追加でgemを管理しなくていい)
- puma.rbをcapistrano3-pumaが自動生成してくれるので、pumaのconfigを書かなくていい
Rails5.0からWebrickを捨ててPumaに移行したのは、
ActionCableを使うのに、スレッドベースのAPサーバが必要だったからですが、
そもそもUnicornとPumaは昔からライバル関係にはあったわけです
とはいえ、Unicornのノウハウが成熟しているに比べ、
Rails5.0RC当時のPumaはまだ詰まるポイントが多く、
ccptsのRails5移行の時にも結構苦労した記憶があります(´-ω-)
それでもやはり、「Rails標準」の勢いは重要で、
久々にCapistranoの記事を探したら、みんなPumaとの連動になっていて、
私も試してみるか・・・と、軽い気持ちだったのですが、これがめっちゃ便利なのです
そもそも、Unicornはmasterプロセスとworkerプロセスが協調して動き、
workerを増減させたり、メモリを使いすぎたら再起動させたり・・・というのが、
無停止で行えるってのがポイントでした*4
とはいえ、そのworkerがDBへのコネクションを持ったままだと、
workerが作り直されるたびにリソースを食い尽くしてしまうので、
おまじないのようにこんなconfigを書いていたはずです
https://github.com/tablexi/capistrano3-unicorn/blob/master/examples/unicorn.rb
毎回そんなのが必要ならば、いっそう自動生成すればええやん・・・というのが、
capistrano3-pumaのえらい点でございます(`・ω・´)
例えば、deploy.rbにこんな感じで書くだけで、shared/puma.rbを自動生成してくれます
(sharedに作ってくれるあたり、実に気が利いてます)
# puma set :puma_threads, [4, 16] set :puma_workers, 0 set :puma_bind, "unix://#{shared_path}/tmp/sockets/puma.sock" set :puma_state, "#{shared_path}/tmp/pids/puma.state" set :puma_pid, "#{shared_path}/tmp/pids/puma.pid" set :puma_access_log, "#{release_path}/log/puma.access.log" set :puma_error_log, "#{release_path}/log/puma.error.log" set :puma_preload_app, true set :puma_worker_timeout, nil set :puma_init_active_record, true # DBコネクション周りのおまじないがこの1行でOK
怖くて試していないのですが、どうもNginx等のconfigも生成できるようで、
ssl用の鍵指定までできるあたり、かなり本格的に使えるように見えます
もちろん、pumaの再起動も「cap [stage] puma:restart」とかするだけですし、
非常に簡単ですねヽ(`・ω・´)ノ
ccptsの場合、元々Unicornのソケットにリクエストを流していたので、
それをPumaに変えるだけ*5で、移行があっさり完了してしまいました
そんなわけで、あくまで運用上の観点から、UnicornをPumaに置き換えたわけですが、
Rails5系を使っていて、まだUnicornで動かしているって方は、
一度Pumaを評価してみてはいかがでしょうか(´・ω・)っ
*1: WebサーバとAPサーバを個別にスケールできない・・・とか、Webサーバで503を返して、APサーバ側を更新したり・・・といった運用を考えると、Passengerはめんどいのです(´-ω-)
*2: その時に書いた記事 https://parrot.hatenadiary.jp/entry/2014/10/29/123509
*3: デプロイのたびにコンテナ構築に10分とかかかり、そこから数百MBのコンテナを転送し、それを本番でpullしてきて・・・という流れを、修正のたびに回すのは(たとえ自動化しても)辛すぎます 「超軽量なコンテナを組み合わせたシステム(それこそ、AmazonのLambdaのような)」なら使いやすいのですが、Railsのような、それ自体が巨大ライブラリの塊で、モノリシックなシステムの場合、あまり向かないのではと(´-ω-) 極端な話、「サーバごとimmutableにしてデプロイ時に差しかえる」という(古典的だけど確実な)手もあるわけですし・・・
*4: Apache等もgraceful restartできますが、まあ置いておいて・・・Σ(・ω・ノ)ノ
*5: 一応、WebのrootをCapistranoを想定したpublicに変えましたが、Pumaに移行したから発生した修正ではないです
Chromeに煽られたので、あまり使ってないサイトもhttps対応する
一年半ぶりの更新になります...φ(・ω・`)
その間にも「チェンクロパーティーシミュレーター」(以下、ccpts)に
大量の技術的な更新を入れていたのですが、
ついつい仕事を優先してBlogを書いてませんでした
- ccptsの主な更新内容
その中のトピックスの一つが「(ccptsの)https対応」で、
昨年の冬くらいに対応し、暗号化方式でなんやかんや悩んだ記憶があるのですが、
とりあえず置いておいて・・・Σ(・ω・ノ)ノ
昨日、Chromeがアップデートされ、「httpは安全ではない」と表示されるようになりました
実質まともに動かしているのはccptsくらいなのですが、
念のためポータル的なサイトの方も対応しておくことに...φ(・ω・`) *1
とはいえ、現在は「Let's Encrypt」という素敵な仕組みが存在し、
やろうと思えば数分でhttps対応が終わってしまうご時世です
ccptsをhttps対応した頃は、まだツール(certbot)の精度がいまいちで、
(特にnginxだと)多少苦労があったり、自動更新がうまくいかなかったりしましたが、
クライアントが「certbot-auto」に移行してからは、さらに簡単になりましたヽ(`・ω・´)ノ
基本的にはここに書いてある通りで、nginxでもapacheでも余裕です
Let's Encrypt の使い方 - Let's Encrypt 総合ポータル
- certbot-autoをwgetしてchmod +x
- certbot-autoを初回起動して必要なライブラリを取得
- certbot-autoをドメイン名つき起動し、証明書発行してもらう
- (途中でメールアドレスを求められるので入力)
- ローカルに生成された証明書をconfに書く
- portをあける
- nginx or apacheを再起動
これだけです(`・ω・´) b
一度証明書を取得してしまえば、環境情報がサーバに保存されるため、
cronにセットしてしまえば、再取得も簡単にできます
$ sudo crontab -l # 週に一回 "certbot-auto renew" を実行する(念のため、実行ログも取る) 2 5 * * 0 /path/to/certbot-auto renew >>/hoge/piyo/certbot.log 2>>/hoge/piyo/certbot_err.log
証明書の期限は90日で、1ヶ月くらい前から更新が可能になりますが、
更新が不要な場合はメッセージだけ出して無視してくれます(`・ω・´)
最悪、cronでの更新がうまくいかなくても、証明書作成時に登録したメールアドレスに、
「もうすぐ期限が切れます」というメールが届くので、手動で更新すれば大丈夫です
(以前はそのような運用をしてました)
あとは・・・問題なければhttpからhttpsにリダイレクトしてあげるといいのですが、
ccptsではやっているものの、ポータルの方はほっといてます
server { listen 80; listen [::]:80; server_name ccpts.parrot-studio.com; return 301 https://$host$request_uri; # redirect to https } server { listen 443; listen [::]:443; server_name ccpts.parrot-studio.com; # 以下略 }
慣れれば本当に数分のレベルですので、業務でいきなり導入する前に、
ぜひ自分のサイトでお試しを(´・ω・)っ
<2018/8/28追記>
cronを使ってnginxの証明書を更新しようとした場合、
nginxのpathを見失って更新できないことがあります
その場合、crontabにPATHを明示する必要があります
$ sudo crontab -l # PATHを明示 PATH=/bin:/usr/bin:/usr/local/bin:/sbin:/usr/sbin # その他必要なPATH # 以下同じ
cronあるあるですが、忘れやすいのでご注意を(´-ω-)
Rails+Reactアプリをbrowserifyからwebpack基盤に移行した件
ちょうど一年ほど前、「チェンクロ パーティーシミュレーター」(以下ccpts)を、
jQueryを使った制御から、React.js + Bacon.jsで大幅に書き換えました...φ(・ω・`)
そもそも、ccpts自体、
「モバイルで動くように*1、できるだけ今風の技術でクライアント側を構築する」
という目的を持って設計してました
昨年の時点ではReactが十分にメインストリームに乗ったと判断して、
基本的なアーキテクチャをReact+browserifyに載せ替えたわけですが、
「Reactで動くようにする」ことが目的だったので、細かいところは置いておいたわけです
それを今回、フレームワークを見直してきれいにした(している)というお話です(´・ω・)っ
解決したい問題とwebpack
昨年末、チェンクロで第3部が実装され、
システムが大幅にアップデートされました
こうなると、ccptsのシステムも3部対応で大幅に書き換えがいるわけですが、
以前構築してからここまで運用してみて、
最大の問題は「デプロイに時間がかかりすぎる」ということです
サーバサイドのデータを書き換える分には問題ないのですが、
クライアント側のjsをいじるとデプロイ時にbuildが必要になり、
私が使っているVPSだと10分以上かかってしまいますΣ(゚Д゚)ガーン
3部対応するにあたり、クライアント側を頻繁にいじる必要があるので、
さすがにこれは許容できません
そもそも、これだけコストがかかると、
「クライアントをいじらずにどうにかできないか?」と考えるようになってしまい、
本来的な設計から遠ざかってしまいます(´-ω-)
そこで、2016年末の時点での「今風のやり方」を探すことにしましたのですが、
その時点で主流になっていた(ように見えた)のがwebpackです
そもそも、Railsは「Asset Pipeline」がよくできすぎています
これを自前で実装するとコストがかかるので、
何とかして今風のコードと組み合わせて乗っけたい・・・というのがポイントになります
調べた結果、ざっくりとこんな感じのやり方が主流になっているように見えました...φ(・ω・`)
- クライアント側のコードを閉じた形で独立して構築する(Railsの管理外)
- webpackで一つのjsにまとめて、Railsのassets管理下に置く(これはpureなjs)
- あとはRailsのprecompile等に素直に任せる
当初、これを自前で書こうと思ったのですが、
もっと楽ができないのか・・・と思ったら、
「React on Rails」という良いものがあったわけです
1年前の記事でも最後に取り上げていましたが、
この時点では何が主流になるのかさっぱりわからなかったので、
そこまでは踏み込みませんでした(´-ω-)
このやり方の最大の決定打は、Rails5.1におけるDHHの方針です
Rails用に仕組みを構築しちゃうあたりがさすがですが、
このwebpackerにreactのオプションも入っているくらいで、
もう迷う要素はなくなりました( ゚д゚)o彡゚
まだRails5.1はリリースまで時間がかかりますが、リリースされたとしても、
React on Railsにはreactのサーバサイドレンダリングという仕組みがありますので、
最終的にはRails5.1の上でReact on Railsという形になると思います
とはいえ、サーバサイドレンダリングをいきなり使うと、
React on Railsに依存しすぎてしまうので、まずは使わずに、
「今風の開発フローを構築するための基盤」として利用しております
実際の段取り
CoffeeScriptの排除とnpmからyarnへの移行
「できるだけクライアントのコードを client 以下に移す」というのが方針になりますが、
es6でクラス定数が使えない等々で、CoffeeScriptで書いたmodelが残っている箇所がありました
これらはAsset Pipelineとbrowserifyで動作していたわけですが、
webpackで一つにまとめるためには、全てes6で書き換える必要があります(´-ω-)
そこで、es6で書くことを優先して、クラス定数の方をあきらめました
(例としてmodelを一つ)
https://github.com/parrot-studio/cc-pt-viewer/blob/69c8e88185536e9c9c8af67c2b5e5caa53069f66/client/app/bundles/ccpts/model/Favorites.js
今回は目的が違うので、この手の妥協は仕方ありません(´・ω・`)
ついでに、npmによるパッケージ管理をやめて、yarnに切り替えました
Railsでbundlerを使っているわけで、この仕組みは全く違和感がありません
import/exportを正しく使う
以前は各modelやcomponentを呼び出す前に、
ブラウザのグローバルにBaconやReactのような各ライブラリを突っ込んでおき、
暗黙的に参照するというやり方でとりあえずやってました
これはCoffeeScriptとes6を共存させるのに必要だったのですが、
CoffeeScriptを排除したので、もう不要です
全部import/exportで書き換えました...φ(・ω・`)
問題は、どうしてもimport漏れが出てくることです
当初、動かしてみてはエラーを見て追加・・・みたいな、
超絶に効率の悪いことをしていたのですが、
ESLintで全部解決しました(`・ω・´)
ルールは自分の直感に反しないレベルでこんな感じに(´・ω・)っ
cc-pt-viewer/.eslintrc at master · parrot-studio/cc-pt-viewer · GitHub
これでエラーが出ないようにガンガン修正していきました
rucobopと同じく、自動で修正する機能もあるので、とても楽です
jQueryの取り扱い
これは1年前と何も変わってませんが、Reactを使うからといって、
jQueryを "使うべきではない" という意見には賛同できません( ゚Д゚)y─~~
たしかに、jQueryで "DOMの操作をすべきではない" ですが、
jQuery界隈には大量のライブラリやノウハウの蓄積があり、
それを全てReactの文脈に落とし込めるとは思えないわけです*2
実際の現場で、jQueryなら数分でできたことが、
Reactだと3日かかります・・・といって、
企画側に納得してもらえませんよね(´・ω・`)
ギリギリまでjQueryを使わないようにしつつ、
どうしてもjQueryが優位な箇所だけ使っていく・・・
それは1年前の時点と変わっていません
しかし、以前はライブラリを全部グローバルに定義していたので問題ありませんでしたが、
全部閉じた環境に持っていこうとすると、jQueryの取り扱いがとたんに難しくなります(lll゚Д゚)
ここに関しては情報も少なく、相当試行錯誤したのですが、
結果的に「古いライブラリは古いやり方、新しいライブラリは新しいやり方」と、
完全に切り分けることにしました
具体的にはこんな感じです...φ(・ω・`)
どうしてもjQueryの文脈が必要なものだけassets以下で管理して、
Reactを含む他の一切をwebpackで一つにまとめたわけです
React側からは「$」がグローバルアクセスできるので、
あとはReactのrefで生DOMにアクセスしてjQueryのアニメーションを適用するだけです
(1年前の記事参照)
グローバル変数があるとESLintが警告を出すのですが、
.eslintrcで「$というグローバル変数だけ許可して」と定義しているので問題ありません
むしろこれにより、「$というグローバル変数が存在する」ということが明確になります(`・ω・´)
置き換えた結果と今後
方針を決めたのが11月で、12月に入ってからは実装されたキャラデータの暫定登録を進めて、
そこから基板入れ替え作業を進めましたが、12月末にはざっくり作業が完了しました
それ以降の開発速度は目に見えて速くなりまして、
やはり心理的な障壁がぐっと下がったのはでかいです(`・ω・´) b *3
コストが下がったことで、棚上げしていた問題、
例えば、Becon.jsのStreamをもっと整理したいとか、
そもそもUIを大きくいじりたいとか、そういうところにも手を出せるようになりました
でも、一番改善したいのはReact on Railsの機能である、
「Reactのサーバサイドレンダリング」なのですが、
そのあたりは今後作業予定なので、できあがったらまた書きます...φ(・ω・`)
Elixirについて調べたついでに、BrainF**kインタプリタを書いてみた
最近、某D社の事例を含めて、Elixirの話題をよく見かけるようになりまして
もちろん、話題になっているからには、
何らかの「時代の要請」があるはずでして、そのあたりを調べてみたわけです
なぜ、Elixirが出てきたのか?
まず読んでみた本はこちらです(´・ω・)っ
- 作者: Dave Thomas,笹田耕一,鳥井雪
- 出版社/メーカー: オーム社
- 発売日: 2016/08/19
- メディア: 単行本(ソフトカバー)
- この商品を含むブログ (2件) を見る
この本は言語の使い方について書いた本ではなく、
「設計思想」について書かれた本でして、
そのこと自体がElixirの出自を象徴しております
歴史の流れを復習してみると、かつてWebはPerlやJavaがメインで、
普通のWebであればだいたいPerlのCGIで書かれていたわけです
でも、ある程度大きな機能になるとPerlではきつくなってきて、
「もっとちゃんとした言語」が求められ、
RailsをきっかけにしてRubyがブレイクしました
Rubyのポイントは、まつもとさん自身が書いているように・・・
- 作者: まつもとゆきひろ
- 出版社/メーカー: 日経BP社
- 発売日: 2016/12/22
- メディア: 単行本
- この商品を含むブログ (2件) を見る
・・・多くの言語が「機能」からの要請で生まれてきたのに対し、
Rubyは「コードをこう書きたい」とか「こう書けると気持ちいい」といった、
「設計思想」を優先して生まれてきた、という経緯があります
その分、(特に1.9より前においては)実行速度に難があったのですが、
システムの大規模化・複雑化や、CPU速度の飛躍的向上により、
メリットがデメリットを大幅に上回るようになったわけです(`・ω・´)
一方で、時代はPCからモバイルへ変わっていき、
「大きなリクエストを素早く」ではなく、
「小さくて大量のリクエストを素早く」という要求に変わっていきました
古いアーキテクチャではこの要請に応えられない*1ため、
Nginxが登場したり、Apacheのアーキテクチャが変わり、
さらなる高速化のためWebSoketやそれに対応するNode.jsが出てきたりしたわけです
そして、CPUのアーキテクチャも変化しました
単純な速度ではもはや向上が見込めないため、
マルチコアにするのが当たり前になってきています
その結果、「小さくて軽い処理(をするAPIサーバ)」が求められるようになり、
Rubyではこれらの状況に対応できない・・・ということはないのですが、
Railsもスリム化できるようになり、Node.jsと組み合わせたりするようになりました
(勘違いしないでもらいたいのは、Rubyがダメということではありません
Rubyの強みは「時代の要請についていくセンス」と、
「変化を許容し、時に古いものを切り捨てる強さ」にあるので)
一方で、「小さく軽い処理のを組み合わせて一つの大きな処理を構成する」方式は、
まさに関数型言語が得意とする領域です
しかし、関数型言語は難しすぎます
古くはLisp・Haskellがありますが、これを「普通の人」が扱うのはとても無理*2です(´-ω-)
その領域をScalaが埋めていくのかな・・・と思っていたのですが、
(少なくとも私は)某D社以外に大きな事例も聞かず、
「Play Framework」のWebにおける大きな採用事例も(少なくとも私は)知りません
そもそも、当の某D社がScalaを切った・・・とまではいかずとも、
「Scalaで書き直しました!」と大々的にアピールしていたシステムを、
すでにElixirで書き直しているあたりで、だいだい状況は推測できます(´・ω・`)
他の関数型言語だと「F#」あたりなのですが・・・
あれは本を読んでみたものの、難易度的にはScalaと大差はなく、
(少なくとも現状では)Windowsっぽいイメージが強すぎてきついです
難易度を無視すれば、「小さなロジックを大量のプロセスで処理する」*3という、
理想的な設計で事例もある「Erlang」も使えるはずなのですが、
パラダイムのジャンプだけでなく、文法的な難しさがつきまといます
こんな状況の中、「関数型言語をもっとなじみがある文法で」、
より端的にいえば「Rubyが書きやすいんだから、Rubyみたいに書けて、
Erlangのように動く言語があればいいんじゃない?」と考えた人がいたわけです
それがまさに「Elixir」でして、冒頭でも書いたように、
Elixir最大のポイントは「こう書けるとうれしい」という、
「設計思想」を優先して生まれた言語である、ということなのです(`・ω・´) b
ElixirでBrainF**kインタプリタを書いてみる
先ほどの本を読んだ感じ、設計思想にはものすごく共感できたので、
とりあえず私にとって「関数型言語におけるFizzBuzz」である、
「BrainF**k」の実行系を書いてみました...φ(・ω・`)
以前もScalaで書いたことがありますし・・・
・・・それより前にはRubyで「実行系を作成する実行系」を書いたりもΣ(・ω・ノ)ノ
- ネタプログラミング言語クリエイターYouma (Gunma.web #8) - ぱろっと・すたじお
- ネタプログラミング言語クリエイター Youma
- BF風言語解析器 Windstorm
で、今回Elixirで実際に書いてみたのがこちらです(´・ω・)っ
大きなポイントは二点ありまして、まず一つ目は
「データの処理の流れを記述する」ということを強く意識させる文法です
def execute(code) do # codeを実行するとは・・・ code # 受け取ったcodeを・・・ |> parse # 解釈し・・・ |> eval # 実行することである end defp parse(str) do # 解釈するとは・・・ str # 受け取った文字列を・・・ |> String.replace("\n", "") # 改行を取っ払って・・・ |> String.codepoints # 一文字ずつに分離し・・・ |> List.foldl([], fn c, li -> li ++ [@cmap[c]] end) # コマンドのストリームに置き換えることである end
この書き方はとてもいいですね(`・ω・´) b
「|>」は前の関数の結果を次の関数の第一引数として渡す、
というだけの構文でしかないのですが、
これだけで「何かしたいのか?」がとても明確になります
それこそが「関数型言語風に宣言的なコードを書く」ことの、
最大の利点であり、それ自体は他の言語でも享受できるのですが、
文法として明確に組み込まれているのがポイントです
もう一点が、「関数の引数自体がパターンマッチになっている」ことです
defp step(:pinc, coms, jump_map, ind, cind, pind, buf, result) do step(coms[cind+1], coms, jump_map, ind+1, cind+1, pind+1, buf, result) end defp step(:pdec, coms, jump_map, ind, cind, pind, buf, result) do step(coms[cind+1], coms, jump_map, ind+1, cind+1, pind-1, buf, result) end # 略 defp step(_, _coms, _jump_map, _ind, _cind, _pind, _buf, result), do: result
Javaでいうオーバーロードに近いのですが、
ScalaやRubyでいうところのcaseやswitchのような、
パターンマッチングを、関数の定義自体でやっています
先ほど挙げた、以前書いたScalaコードを見てもらえばわかるのですが、
stepに相当するところの中でパターンマッチングしていて、
また自身を再帰的に呼び出しています
そのため、stepの処理自体が肥大化しているし、
新しいコマンドを追加する場合、case文のマッチングを増やさないといけません(´-ω-)
その点、Elixirのコードは一つ一つの関数が「定義」として小さくまとまっており、
「何をしたいのか?」が明確になって、見通しが良くなっています
「これだけのこと」ではあるのですが、
それでも「こう書けるととても楽」というのを優先したのがElixirでして、
まさにそれはRubyと同様の設計思想なわけです(`・ω・´)
Elixirは「使える」のか?
こんな感じで、私の好みにあうElixirですが、
今の私の仕事である「アーキテクト」の立場からすると、
少なくとも今は「ない」かな・・・とΣ(゚Д゚)ガーン
弊社CTOとも話をして、「この言語はいいね(`・ω・´)」という話にはなっていますが、
あくまでお仕事として考えると、あまりにもコストが高いかなと思います
「表面的な文法」はRubyに似ているため、
例えばRubyの初期のように「PerlのようにRubyを書く」のと同様、
「RubyのようにElixirを書く」のは可能ですし、私もそのように導入します
しかし、Elixirの利点を最大限の享受するには、
「関数型的な設計思想」が必須になります
ElixirがRubyに強く影響を受けているのと同様に、
ElixirのWebフレームワークである「Phoenix Framework」は、
とてもRailsの影響が強いです
「ある程度わかっている人ならば」Railsの仕組みをこれに移すことも可能かもしれません
しかし、RubyもRailsも最大のポイントは、先ほど書いたように、
「時代の要請についていくセンス」と
「変化を許容し、時に古いものを切り捨てる強さ」です
RubyはどんどんVMが高速化されていますし、
文法的にも「型」を導入しようとしています
(普通の言語なら危険信号なのですが、まつもとさんならまず大丈夫でしょう)
Railsもモジュール化・スリム化(API専用モードなど)が進み、
並行処理を意識したライブラリを組み込んだりもしています
なにより、個人的な感覚では、「ElixirでRDBにアクセスすること」自体が、
せっかくのメリットを台無しにしていると思いますΣ(・ω・ノ)ノ
Node.jsの仕事をしたときに強く感じたのは、
とにかく「全てを非同期に書かなければならない」ということであり、
「同期的な処理をどう切り離すか?」を常に考えないといけません
こういったことを意識しなければならないのは、
少なくとも「普通のWebシステム」においてはオーバースペックですし、
例えばRubyでもそういった設計思想のシステムは十分組めます*4
あとは人的コストの問題です
Node.jsを書いたことがある人はそこそこ探せますし、
Rubyを書ける人なら(質を問わなければ)十分見つかるでしょう
(なぜ、FBような大規模システムで、PHPが使われているのかを考えてみてください)
今後、Elixirが何らかの形でメインストリームに乗った場合*5は別として、
現時点では「あえてRubyを切り捨てる」メリットがあまりに薄いのかな・・・と思います
メインのWebサーバをRuby+Railsで組み、
リアルタイム性を求められる領域をNode.jsで構築する、というのが、
今の時点では「普通にいけそうなWebシステム(=長く運用できるシステム)」だと思います
逆に「普通じゃないWebシステム」、
例えば「大量の動画をバックエンドでリアルタイムにエンコードし続けるシステム」ならば、
かなり有効に作用すると思います
・・・が、そういうシステムを必要とする会社は限られてますし、
そういう会社はシステムにかけられるコストが桁違いなので、
やはり「普通の会社」では難しいですよね・・・(´・ω・`)
*1: いわゆるC10K問題
*2: ここでいう「無理」とは、「扱える人がいない」のではなく、「扱える人を雇うコストが、一般のWeb企業において現実的ではない」くらいの意味で
*3: このプロセスをクラウド上で起動するのが「サーバレスアーキテクチャ」とか話題のあれですね
*4: というか、以前仕事で組みました http://parrot.hatenadiary.jp/entry/2013/09/03/112657
*5: 個人的にはそうあってくれると楽しいのですが・・・(´-ω-)