彩湖サイクリング

先週末はびア部のなかじまさん、ひらやさんと荒川の彩湖に行ってきました。 ひらやさんはロードが届いてから初参加。

コース

garmin connect

体調が良くなく、様子を見ながらの集合でちょっと遅れて9:30スタート。 荒川沿いを通らず、埼玉をうろうろして11:00くらいに彩湖到着。 適度に休みつつ、ぐるぐる回って17:00くらいに帰宅しました。

ライトな感じでーということで彩湖になったのですが、結局トータル70kmくらい走りました。 体調悪かったものの、心肺も脚も意外と調子がよかったので無理せず走れました。あと、明るいうちに帰って来れたのもよかった。

荒川は何度も通ってるんですが、彩湖に行ったのは初めてです。 景色はちょっと単調ですが、道も広く適度にアップダウンがあるので走るのは楽しいです。 風も強くなく晴れていたので、他のローディーの方もたくさんいました。

近くの公園にはhaskellっぽい遊具もあって、エンジニアの運動にはぴったりな場所です(?)。

注意点というほどのことでもないですが、コンビニやご飯食べれそうなお店が周囲に少なそうだったので、行くときは事前に用意しておいた方がいいかもです。

装備

ほとんど前回と同じ格好でしたが、日中のみの走行だったので寒くはなかったです。 むしろ暑いくらい?

安売りしてたので、今回は新しくシューズカバーも装備して行きました。おかげで足も寒くなかったし、風にも勝てた気がします![要出典]

その他

もう春も間近なので、これからの季節のサイクリングが楽しみです。

知り合いも続々と自転車を購入してるようだし、ロード仲間の友達が近所に引っ越してくるので(徒歩5分!)、今年はたくさん出かけようと思います。

elastic beanstalkの設定ファイルを活用する

最近AWSを使う仕事がちょくちょくあります。

そのうちの一つに、git submoduleに多数依存しているプロジェクトをelastic beanstalkに移行するというものがありました。

elastic beanstalkではデプロイするだけではsubmoduleを自動で取ってきてくれないので、依存するsubmoduleをプロジェクトの管理下に直接含めたり、デプロイ時に手動で更新するなどの対応が必要です。

今回はこれをelastic beanstalkの設定ファイルを使って解決してみました。

elastic beanstalkの設定ファイル

設定ファイルはyaml形式のファイルで、プロジェクトルートに.ebextentions/*.configとして置くことで利用することができます。

containerの種類やOSによって設定できることが多少異なりますが、どれもとても便利です。 例えば、linuxの場合、

  • パッケージマネージャからパッケージのインストール
  • ユーザやグループの作成
  • ファイルの新規作成/配置
  • シェルのコマンドの実行
  • サービスの設定
  • コンテナの設定

などが色々なことができます。

詳しくは下記ドキュメントを参照。

git submoduleを使ったプロジェクトのセットアップ例

今回は以下のような設定ファイルを書いてみました。

# .ebextentions/01-git-submodule.config

container_commands:
  10-rm-dirs:
    command: 'rm -r submodules/hoge src/fuga/piyo'
  11-git-init:
    command: 'git init'
  20-git-add-1:
    cwd: '/var/app/ondeck/'
    command: 'git submodule add git://github.com/hoge/hoge.git  submodules/hoge'
  21-git-checkout-1:
    cwd: '/var/app/ondeck/submodules/hoge'
    command: 'git checkout e1ad2bde9c2cf12e65a89ef5eadcccd048f1af0c'
  30-git-add-2:
    cwd: '/var/app/ondeck/'
    command: 'git submodule add git://github.com/fuga/piyo.git src/fuga/piyo'
  31-git-checkout-2:
    cwd: '/var/app/ondeck/src/fuga/piyo'
    command: 'git checkout d49febf9eaf66bd89205f1e39b0290dfcbec5dfd'

やっていることとしては、

  1. git initする
  2. submoduleの展開先ディレクトリを削除する
  3. git submodule addし直す
  4. 指定したコミットをチェックアウトする

という感じです。

各submoduleの設定は.git/configに書き込まれているのですが、デプロイするプロジェクトには.gitが存在しないため上記ではその代わりの作業を行っています。

submoduleが増えるたびに追記する必要があるのでちょっと泥臭いですが、それ以外では余計な運用が増えないメリットもあります。

知っとくといいかもしれないこと

ディレクトリ

container_command実行時には/var/app/ondeckがワーキングディレクトリとなっています。

ここはプロジェクトのzipが展開される準備用のディレクトリで、ここから/var/app/currentにコピーされてデプロイが完了します。

設定ファイルの適用履歴

設定ファイルの適用内容はログに書き出されるので、設定失敗時に見ると役立ちます。

ログはelastic beanstalkのマネジメントコンソールからLogsページでSnapshot Logsで取得できます。

まとめ

こんな感じで環境設定をテキスト管理できるのはなにかと便利ですね。 ただし設定ファイルはデプロイ毎に実行されて時間がかかるので、AMIも同時に活用するとよりいい感じだと思います。

elastic beanstalkは最近使い始めたので知らないことだらけです。 今回のgit submoduleのやり方を含め、「こうすると便利だよ!」という情報があればぜひ教えてください!

play-slickを使う

先日play-slickを使ってみたのでそのメモ。

play-slickとは

その名の通り、playframeworkでslickを使うためのプラグインです。

play-slickを使うと

  • playframeworkのデフォルトのDB設定をそのまま使い回せる
  • slickのスキーマ変更に合わせて自動でevolution用のmigrationファイルを作成してくれる
  • セッション周りの扱いが楽になる

といったメリットがあります。

ちなみにplay-slickはplayframework 2.3に統合される予定とのこと。

環境

今回試した環境です。

  • playframework 2.2.1
  • scala 2.10
  • slick 2.0.0
  • play-slick 0.6.0.1
  • mariadb 10.0.8

セットアップ

playframeworkのセットアップは省略。

プラグインの追加

まずbuild.sbtに利用するプラグインを追記していきます。

// build.sbt

libraryDependencies ++= Seq(
  // other plugins
  // ...
  "com.typesafe.slick" %% "slick" % "2.0.0",
  "org.slf4j" % "slf4j-nop" % "1.6.4",
  "com.typesafe.play" % "play-slick_2.10" % "0.6.0.1"
  "org.jumpmind.symmetric.jdbc" % "mariadb-java-client" % "1.1.1",
)

ここで注意が必要なのはplay-slickのバージョンで、playframeworkとslickのバージョンによって使えるバージョンが異なるようです。

playframework slick play-slick
2.3.x 2.0.x 0.7.x
2.2.x 2.0.x 0.6.x
2.2.x 1.0.x 0.5.x
2.1.x 1.0.x 0.4.x

(2014-07-27編集: 更新された公式ドキュメントに合わせて修正しました)

DBの設定

通常のplayframeworkアプリケーションと同様に、application.confにDBの接続情報を追記します。

mariadb用の設定となっているところは適宜変更してください。

# conf/application.conf

db.default.driver=org.mariadb.jdbc.Driver
db.default.url="jdbc:mariadb://localhost/dbname"
db.default.user=dbuser
db.default.password=dbpass
slick.default="models.*"

ここでslick.defaultというパラメタを指定していますが、これはevlolution用にmigrationファイルを作成する対象のクラスを指定するために利用されるそうです。

今回はmodelsパッケージ以下にslickの用クラスを定義するためそのように指定しています。

modelの実装

idと名前を持つユーザ用のテーブルとそれを操作するクラスを考えたときに、基本形はこんな形になると思います。

// app/models/user.scala
package models

import play.api.db.slick.Config.driver.simple._

case class User(id: Option[Int], name: String)

class Users(tag: Tag) extends Table[User](tag, "users") {
  def id = column[Int]("id", O.PrimaryKey, O.AutoInc)
  def name = column[String]("name", O.NotNull)

  def * = (id.?, name) <> (User.tupled, User.unapply _)
}

object Users extends DAO {
  def findById(id: Int)(implicit s: Session): Option[User] = {
    Users filter { _.id === id } firstOption
  }

  def insert(user: User)(implicit s: Session) {
    Users += user
  }
}

まずユーザ情報をUserケースクラスとして実装し、usersテーブルの表現としてTable[User]を継承するUsersクラスを実装しています。これはslickのLifted Embeddingの書式そのままです。

今回はここでUsersクラスのコンパニオンオブジェクトとしてDAOを定義しています。 controllerはこのDAOを利用することでロジックをcontrollerに持ち込ませなくて済みます。

このコンパニオンオブジェクトが継承しているDAOは以下のように定義しています。

// app/models/DAO.scala

import scala.slick.lifted.TableQuery

private[models] trait DAO {
  val Users = TableQuery[Users]
  val Posts = TableQuery[Posts]
  ...
}

こうすることで他のテーブルを跨ぐクエリ発行するメソッドを実装できるようになります。

controllerの実装

// app/Application.scala

package controllers

import models._
import play.api.db.slick._
import play.api.mvc._

object Application extends Controller {
  def index = DBAction { implicit rs =>
    Ok(views.html.index(Users.findAll))
  }
}

controllerではDBActionを使うことによりセッションの生成を自動で行ってくれます。 そのためDAO側ではimplicit session: Sessionを受け取るよう定義する必要があります。

まとめ

以上のようにセッション周りを考えずにDBを扱うことができるので、ロジックに集中して実装できると思います。

おまけ: slick-playとCake Pattern

play-slickにはProfileというcake patternを簡単に実装するための仕組みがあり、公式サンプルに実装例があります。

ただこのサンプルだとcontrollerで直接slickのクエリを利用していてあまりイケてない感じだったのでDAOを使う方式に書き直してみました。 が、DAOを継承するクラスが増えるたびにインスタンスを作らなければならなくなり、微妙な感じに。

良いやり方があったら教えてください!

# conf/application.conf

slick.default="models.current.dao.*"
// app/models/user.scala

package models

import play.api.db.slick.{Profile, Session}

case class User(id: Int, name: String)

trait UserComponent { this: Profile =>
  import profile.simple._

  class Users(tag: Tag) extends Table[User](tag, "users") {
    def id = column[Int]("id", O.PrimaryKey)
    def name = column[String]("name", O.NotNull)

    def * = (id, name) <> (User.tupled, User.unapply _)
  }
}

class UsersDAO(implicit dao: DAO) {
  import dao._
  import profile.simple._

  def findByName(id: Int)(implicit s: Session): Option[User] = {
    Users filter { _.id === id } firstOption
  }

  def insert(user: User)(implicit s: Session) {
    Users += user
  }
}
// app/models/DAO

package models

import scala.slick.driver.JdbcProfile
import scala.slick.lifted.TableQuery
import play.api.db.slick.{Profile, DB}

class DAO(override val profile: JdbcProfile) extends UserComponent with Profile {
  val Users = TableQuery[Users]
}

object current {
  implicit val dao = new DAO(DB(play.api.Play.current).driver)
  val Users = new UsersDAO
}