ansibleでgolangをインストールするレシピ

グローバルにgoをインストールするレシピを書きました。
一応冪等性も確保。

    # /usr/local/go のディレクトリ有無を確認。ディレクトリが無いときだけ以下のレシピを実行する。
    - stat: path=/usr/local/go
      register: godir
    - name: get golang
      command: wget https://storage.googleapis.com/golang/go1.5.linux-amd64.tar.gz -O /tmp/golang1.5.tar.gz
      sudo: yes
      when: not godir.stat.exists
    - name: tar golang
      command: tar zxf golang1.5.tar.gz chdir=/tmp
      sudo: yes
      when: not godir.stat.exists
    - name: install golang
      shell: mv /tmp/go /usr/local/go-1.5; ln -s /usr/local/go-1.5 /usr/local/go; echo 'export PATH=$PATH:/usr/local/go/bin' > /etc/profile.d/go.sh
      sudo: yes
      when: not godir.stat.exists

GOPATHは適宜設定しましょう。
(syntaxhighlightのwordpressプラグインってyamlはハイライトできないのね。。)

ninja frameworkとplayの比較

play2系を触ってみて1系から劣化したなーと思うところがninjaではどうなってるか見てみました。

環境毎の設定切り替え

play2系ではprefixによる設定の切り替えは廃止され、環境別の設定ファイルを用意する仕様になりました。
個人的には、小規模で使う分には1ファイルに全環境の設定を書いておきたいので、若干劣化したなぁ、という感覚でした。
ninjaはほぼほぼplay1系と同じ仕様で、起動時に渡す変数をprefixとして設定を切り替える形です。こちらの方が使いやすいかなー。

http://www.ninjaframework.org/documentation/configuration_and_modes.html

  • 起動時に ninja.mode を設定。
    mvn ninja:run -Dninja.mode=dev
    

  • application.conf

    database.name=database_production
    %dev.database.name=database_dev
    

自動実行ジョブ

play1系ではQuartzを使い、cronライクにシンプルに実装できましたが、
2系ではAkkaベースとなり設定の記述が複雑になってしまい、使いづらくなりました。
また、cronのように指定のタイミングで実行するのではなく、起動時から定期的に実行するだけの仕組みになりました。
ninjaではどうなったのか調べてみました。

http://www.ninjaframework.org/documentation/scheduler.html

  • Jobクラス
    @Singleton
    public class ScheduledAction {
        @Schedule(delay = 60, initialDelay = 5, timeUnit = TimeUnit.SECONDS)
        public void doStuffEach60Seconds() {
            // do stuff
        }
    }
    

  • Job登録

    public class Module extends AbstractModule {
        protected void configure() {
            bind(ScheduledAction.class);
        }
    }
    

んー、記述は簡単ですが、できることはplay2系と同じですね・・・
cronのような仕組みはあまり好まれないんですかね?

Session

http://www.ninjaframework.org/documentation/basic_concepts/sessions.html

playのSessionはJava ServletのHttpSessionとは全く異なる仕組みで実装されており、
サーバーには何も持たず、Cookieにすべてのコンテキストを保存します。
これはplayの思想であるshared nothingを実現するため、サーバー間でコンテキストを共有しないようにするためです。

ninjaもこの思想は引き継がれているようで、ninjaのSessionオブジェクトに値を設定すると、ブラウザのCookieに保存する仕様になっていました。
小規模ならLBでスティッキーにしてサーバーに全部保存したいんですが、やはりninjaでもサーバ側で保持したいコンテキスト情報は、Memcached等を併用する必要がありそうです。

Bootstrap4

alphaですが、Boostrap4が公開されていました。

http://v4-alpha.getbootstrap.com/

3系が長かったので、もうメジャーアップデートは無いのかなと安心していたらこれですよ。
ぱっと見はあまり3系から大きな変化はないようで、Blogの記載で気になったところは、

http://blog.getbootstrap.com/2015/08/19/bootstrap-4-alpha/

  • less から sass に移行
    lessもsassも使いこなせている訳ではありませんが、sassの方が優れているのでしょうか。
    Railsのフロントエンドで少しsassを使っている程度なので、そろそろ真面目に勉強してみようかな。

  • IE8のサポート終了
    以前から完全ではありませんでしたが、正式にIE8はサポートされなくなるようです。
    コンシューマサービスではあまり影響ありませんが、
    業務システムではIE8が現役のところも未だにあると思うので、これを機に情シスはWindows10を導入してIEからEdgeへ!(死にそう

Xamarin のライセンスが無償で取得できるかも

https://blog.xamarin.com/free-xamarin-subscriptions-for-windows-phone-developers/

8/17 以前にWindows Phone向けのアプリをリリースしたことがある開発者に対して、
Xamarin のライセンスを取得できるそうです。
Xamarin は.NETでiOSやAndroid向けモバイルアプリケーションを開発できるクロスプラットフォーム開発環境です。
ネイティブ実行できるバイナリを出力するそうで、高速に動作するのが売りだそうです。

ちなみにこのライセンスの取得は 8/31 までだそうです。
私も申請してみました。ライセンスが提供されたら何か作ってみよう。

マイクロフレームワークなしがし Spark編

マイクロフレームワークなにがしにて、ninja framework を触ってみましたが、
今度は Spark というフレームワークにトライしてみようと思います。

Webサイト

http://sparkjava.com/

セットアップ

https://sparktutorials.github.io/2015/04/02/setting-up-a-spark-project-with-maven.html

チュートリアルの通りにやると、POMを書いてmain関数にルーティング(+コントローラ)を記述するだけですね。
ninjaはplayライクなフレームワークでしたが、sparkはsinatraを模倣したフレームワークのようです。

import static spark.Spark.*;

public class Main {

	public static void main(String[] args) {
		get("/", (req, res) -> "hello world");
	}
}

main関数をjavaコマンドなどで通常のjavaアプリケーションとして実行します。
すると、Webサーバーが起動するので、ブラウザで http://localhost:4567 を開いて、hello worldが表示されればOK。

hello world

Viewのカスタマイズ

上記はレスポンスをそのまま返すだけなので、
今度はviewをテンプレート化します。

各種テンプレートエンジンの実装は、jarに分離されているので、下記を参考に、pomに依存関係を追加します。
http://sparkjava.com/documentation.html#views-templates

今回は、FreeMarkerを使った場合を試してみました。

  • Model
    public class Book {
    	private String name;
    	public String getName() {
    		return name;
    	}
    	public void setName(String name) {
    		this.name = name;
    	}
    }
    

  • Controller

    public class Main {
    
    	public static void main(String[] args) {
    		Book book = new Book();
    		book.setName("日々創造");
    
    		get("/", (req, res) -> new ModelAndView(book, "book.ftl"), new FreeMarkerEngine());
    	}
    }
    

  • View (src/spark/template/freemarker/book.ftlに配置するのがポイント)

    book name: ${name}
    

  • 出力結果

    book name: 日々創造
    

テンプレートのディレクトリがデフォルトで spark/template/freemarker になっているので注意。
sinatraライクに書きたい方はsparkはオススメなのではないでしょうか。
個人的にはninjaの方が好みかも?

マイクロフレームワークなにがし Ninja編

最近Play frameworkに影響されたフレームワークがいくつか出ているようで、
マイクロフレームワークと呼ばれているようです。

Ninja Framework

今日ははその一つである Ninja framework を見ていきたいと思います。
とりあえず公式サイトに沿ってチュートリアルを動かしてみます。

http://www.ninjaframework.org/documentation/getting_started/create_your_first_application.html

mvn archetype:generate -DarchetypeGroupId=org.ninjaframework -DarchetypeArtifactId=ninja-servlet-archetype-simple
cd ninja-servlet-archetype-simple
mvn clean install
mvn ninja:run

これでとりあえず開発サーバが起動します。
Playのようにバイナリパッケージを展開してコマンドにPATHを通したりといった作業が必要ないところはいいかも。
ちなみに開発サーバのポートを変えたい場合は下記の通り。

mvn ninja:run -Dninja.port=8081

ブラウザで localhost:8081 を開くと初期状態のHomeが表示されます。
最初からwebjarsのbootstrapが組み込んであって好印象。

DBアクセス

続いてDBアクセスを試します。
これもチュートリアルに書いてあるとおり。
http://www.ninjaframework.org/documentation/working_with_relational_dbs/jpa.html

テーブルの作成はpersistence.xmlに下記の設定を追加し、JPA(Hibernate)に任せるのが基本的には楽ですが、

<property name="hibernate.hbm2ddl.auto" value="update"/>

自前でマイグレーションSQLを用意してテーブルを作成したい場合は、
Flyway というライブラリに則ってやる事も可能なようです。

  • conf/application.confに追記
    ninja.migration.run=true
    

  • db/migration/V1__hogehoge.sql
    create table Hoge (
      id int(11) primary key,
      name varchar(255)
    );
    

自動的にV1__hogehoge.sqlがDBに適用されます。テーブル名がおかしい場合など、自動的にFlywayがバリデートしてくれるようです。
個人的には開発中はHibernateの自動スキーマ作成に頼り、運用中は検証済みのDDLファイルを慎重に適用したい派なので、Flywayを使うことは無いかもしれません。
(Flywayのバリデートで謎のエラーが出て地味にハマったので・・・)

ちょっと触った感想

  • Controller,ViewがPlay1系に近い感じで好印象
  • DB周りは素のJPAなので、取っつきやすい反面、PlayのModel程は使いやすくなさそう
  • バイトコードをいじらないようなので、若干安心

しばらく触ってみたいと思います。

Scalaを使ったプラガブルな実装

昔作ったScalaの言語構文とEvalの仕組みを利用した自作プラグインの作り方を紹介します。

Evalする部分については、こちらの記事を参考にさせて頂きました。
http://d.hatena.ne.jp/xuwei/20140607/1402128646

package parsers

import scala.reflect.runtime.currentMirror
import scala.tools.reflect.ToolBox
import java.io.File
import scala.io.Source

object Parser {
  def eval[T](code: String): T = {
    val toolbox = currentMirror.mkToolBox()
    val tree = toolbox.parse(code)
    toolbox.eval(tree).asInstanceOf[T]
  }

  def evalFile[T](file: File): T = {
    eval[T](Source.fromFile(file).getLines.mkString("\n"))
  }
} 

次に、Pluginの基底クラスを用意します。

package plugins

trait Plugin {
  def abstractMethod()
}

次は、Pluginの実装ファイル(PluginImpl.scala)を用意します。

new plugins.Plugin {  
  def abstractMethod() {
    println("hello")
  }
}

最後に、動的にファイルを実行してPluginをインスタンス化し、Plugin#abstractMethodを呼び出すメインとなるコードを実装します。

object Main {
  def main(args: Array[String]) {
    val pluginFile = new java.io.File("./PluginImpl.scala")
    val plugin = parsers.Parser.evalFile[plugins.Plugin](pluginFile)
    plugin.abstractMethod
  }
}

scalaファイルはコンパイルしてjarにし、PluginImpl.scala を同じ階層に配置して実行してみます。

$ java -jar Main.jar
hello

実際のシステムに実装する際には、特定のディレクトリ以下の.scalaファイルをすべて読んでインスタンス化したり、
一度読んだ後も、定期的にファイルのタイムスタンプをポーリングし、アプリケーションの再起動なしにプラグインをリロードできるようにしていました。
タイプセーフな設定ファイルなどもこの仕組みを使って実装し、システムを堅牢にするのに一役買っていました。

使いにくいScalaライブラリ

ScalaのHTTPライブラリってイケてないと思うんです。
何が気にくわないかというと、全体的にScalaのライブラリって、俺俺DSLを開発していて、
本当に使い方が分かりにくいと思うんです。

たとえば昔の Dispatch。(極端な例として、dispatch-classicのサンプルを記載します。)

// ex) http://dispatch-classic.databinder.net/URLs+and+Paths.html
import dispatch._
val sl = :/("www.scala-lang.org")
val learnScala = sl / "node" / "1305"

// ex) http://dispatch-classic.databinder.net/Response+Bodies.html
Http(learnScala >- { str =>
  str.length
})

もはやね。なんのこっちゃですよ。暗号ですか。
POSTリクエストを送りたくなってもどうしたらよいか全く想像がつきません。

私は、サンプルからオプションの使い方まで想像できるのが
使いやすいライブラリと考えています。

新しいDispatchではこのあたりは割と改善されているのですが、
ORMapperのSlick等も含めて、分かりにくい俺俺DSLを作り上げてしまうのはなんとかなりませんかね。
ライブラリの利用を通じて、タイプ量が少ない、Syntaxが短い事と、
シンプルで使いやすい事はイコールではない、ということを痛感しました。

AWS Lambdaで1分以上かかる処理をする

AWS Lambda、便利ですね。
サーバーレスでプログラムを実行できるのが魅力的なのですが、
実行時間が1分に制限される等、いくつかの制約で採用を見送る事もあるかと思います。

1分以上かかる処理はEC2等で実行しましょう、という事なのでしょうが、
どうしてもAWS Lambdaで処理させたい、というシチュエーションもあるかと思いますので、その制限を回避する方法を調査しました。

再帰呼び出しする

AWS Lambdaを起動するAPIが提供されているので、
LambdaスクリプトからLambdaを呼び出し、処理を引き継ぐことで、1分以上の処理を行うことができます。

var aws = require('aws-sdk')

exports.handler = function(event, context) {
  var count = event.count || 0
  count += 1
  console.log("count:" + count)
  
  // count が3を越えたら終了。
  if (3 < count) {
    console.log("count end.")
    context.succeed()
  } else {
    var lambda = new aws.Lambda()
    var params = {
      FunctionName: "recall",
      InvokeArgs: '{ "count": '+count+'}'
    }
    // countをパラメータに引き渡して自分自身のLambdaを再帰呼び出し。
    lambda.invokeAsync(params, function(err, data) {
      if (err) {
        console.log("error!")
        console.log(err)
      } else {
        console.log("next count:" + (count + 1))
      }
      context.succeed();
    })
  }
};

当然ながら、再帰実行する場合は、きちんと終了するフラグを立てるように実装しないと無限に再帰実行されます。
課金が発生するので、もしこの再帰Lambdaを実装する場合は慎重にテストしてください。
もし無限呼び出しが発生したら、Lambda関数の登録を削除する事で強制停止することは可能です。

また、Lambdaを実行するIAM Roleにlambda関係の権限を付与しておいてください。
(自動で作成されるlambda_basic_executionにはついていません。)

制約は多いものの、非常に安価にサーバーレスプログラムを実行することができるので、是非使っていきたいですね。

マイクロサービスの是非

巷ではマイクロサービスが流行っているようです。
モノシリックなアプリケーションに比べると、部分的なデプロイが容易になるなど、いくつかのメリットがあるようです。
すでに語られている内容ですが、個人的な見解をまとめておきます。

過去の経験としては、大規模になるにつれて、スケーラビリティや可用性を考慮すると、自然とマイクロサービスに近くなる気がします。
部分的にスケールアウトしたかったり、システム全体への影響のあるコード変更を避けたくなるからです。
多くの場合は以下のようなメリットを求めてマイクロサービスを採用するでしょう。

マイクロサービスのメリット

  • 部分的なスケールアウトを容易にしやすい。
  • コードの影響範囲を局所化してシンプルに保ちやすい。
  • 将来的に他のシステムから利用する可能性がある機能を切り出しておきたい。
  • 開発チームのパラレル化。

しかしちょっと待ってください。
一見、現代的な要求を満たしているマイクロサービスですが、
モノシリックなアプリケーションに比べると、多くのデメリットを潜在的に抱えています。

マイクロサービスのデメリット

  • 事前の設計如何によってコードが重複しやすい。
  • サービス間をまたがるリファクタリングがしにくい。
  • プロセス間通信によるオーバーヘッド。
  • 障害ポイントの増加→監視対象・運用作業の爆発。
  • デプロイ手順の複雑化。
  • テストの難度上昇。

特に運用面の問題は高確率で発生します。
モノシリックなアプリケーションに比べると設計の重要性はあがるでしょう。
(設計次第では、マイクロサービスのメリットを享受できない事もあるでしょうね)

本当の目的

個人的にマイクロサービス化するにあたり、重要だと考えているのは、以下の一点です。

  • システムの一部分が外部から利用される可能性があるのか。

「サービス」をマイクロ化しても、利用するのが同居するシステムのみの場合、
結局システム全体をデプロイするケースが多かったりして、あまりメリットが得られない事もあります。
マイクロサービスの採用を検討中の方は、この1点においてシンプルに考えてみても良いかもしれません。