リラクのサーバサイド事情 with Scala - Re.Ra.Ku アドベントカレンダー day 2

Re.Ra.Ku アドベントカレンダー 2日目です。

こんにちは。ヘルステックチームの近藤です。開発では主にサーバサイドを担当しています。

ところでみなさん、リラクのサーバサイドに使われているプログラミング言語をご存知でしょうか?ご存知の方は通ですね。ご存知でない方はこれを機に興味を持っていただけると幸いです。リラクではScalaを採用しています。

ですので、サーバサイド担当の私の話はほとんどがScalaの話です。しょうがないですよね。そもそも私Scala大好きですし。

ということで、リラクのサーバサイド事情 with Scalaと称して、Re.Ra.Ku アドベントカレンダー 2日目もといRe.Ra.Ku Scalaアドベントカレンダー 1日目です。と言いつつ、サーバサイドの一般的な話が多めなので、Scala成分は少し薄めです。

ちょとした方針

サーバサイド事情とか言っていますが、特に変わったことはしていません。クライアントからのリクエストはHTTPで受け付けていますし、データベースにはRDBを利用しています。よくあるものです。

ただ弊社では以下の方針を執っています。

JSONしか喋らない

サーバサイドと言うと範囲が広いのですが、実際はほぼアプリ(クライアント)のAPIサーバーしか書いていません(あとは非同期処理用のワーカーとか)。ですので、HTTPのレスポンスボディはJSONです。

ただし、アプリはスマートフォンアプリに限らずウェブアプリも指しています。ウェブアプリは静的ファイルによるSPAになっていて、そのファイルをScalaから出力することはありません。

よって本当にJSONしか喋りません。

操作指向のURL

JSONしか喋らないとは言え、プロトコルはHTTPですので、当然リクエストメソッドやURLという概念があります。

APIサーバーを書くときにRESTを採用することって結構あると思います。http://example.com/users/1 というURLで「ユーザID: 1のユーザー」というリソースを表し、そのURLへのGETで情報の取得、PUTで更新、DELETEで削除といったやつですね。

しかし弊社ではRESTのようなリソース指向のURLにせず、操作指向のURLにしています。

操作指向のURLとは何か。「ユーザID: 1のユーザー」の情報を取得する場合は GET http://example.com/getUser?id=1 とし、更新する場合は POST http://example.com/updateUser とするものです。

なぜ操作指向のURLを採用したか、ですが:

  • リソース指向のURLでは表現に無理が生じる(ときがある)*1
  • クライアントが求めるものは画面に表示するための情報(レポート)であり、リソース指向だとひとつのAPIで得られる情報に過不足が生じる

あたりが理由です。もちろん広く公開するAPIならば誰が利用するか分からないのでリソース指向になるでしょう。しかし、クライアントが求めている情報が明確なのであれば、それ専用のAPIを作ってしまった方が得策です。そうなってくると操作指向のURLの方が都合が良くなってきます。

アーキテクチャ

アーキテクチャは一応レイヤードアーキテクチャを採用しています。何故「一応」なのかと言うと、使っている言葉はレイヤードアーキテクチャのものなのですが、依存関係逆転の原則などを用いて依存方向を厳密にしているので、実態はオニオンアーキテクチャやクリーンアーキテクチャの様相を呈しているためです。が、一応レイヤードアーキテクチャと言っておきます。

ドメイン層はDDDによってモデリングしています。

アプリケーション層はドメイン層のモデルを活用してクライアントが直接利用するアプリケーションサービス(ユースケース)を実現しています。

プレゼンテーション層はHTTPリクエストを解釈し、アプリケーションサービスが求める形に変換、アプリケーションサービスから得られた結果をHTTPレスポンスに変換しています。

そしてインフラストラクチャ層はドメイン層のインタフェースを実装しています(RDBへの永続化、外部ウェブサービスとのHTTP通信など)。

レイヤードアーキテクチャを採用した理由ですが、少なくともトランザクションスクリプトやアクティブレコードパターンでは自身の首を締めることになるだろう、という考えがあったからで、あまり積極的な理由はありません。実際今のアーキテクチャで不自然なところもあったりするので、それは今後の開発で次第に変えていくと思います。

プロジェクトの分割

ビルドツールにはsbtを採用しています。ですので、ディレクトリ構造もsbtに準拠しています。

ただし、よほど単純なものでない限りは必ずマルチプロジェクトにします。以下は実際に使っている project/build.scala を簡略化したものです。

// project/build.scala

import sbt._
import sbt.Keys._
import org.scalatra.sbt._
import org.scalatra.sbt.PluginKeys._
import com.earldouglas.xwp.JettyPlugin

object ServerBuild extends Build {
  val serverSettings = Defaults.coreDefaultSettings ++ Seq(
    organization := "jp.co.reraku",
    version := "0.0.1",
    scalaVersion := "2.11.8"
  )

  // ルートプロジェクト: 他のすべてのプロジェクトをまとめているプロジェクト
  lazy val server = Project(
    id = "server",
    base = file("."),
    settings = serverSettings
  ) aggregate(domain, jdbcImpl, appApi, adminApi)

  // アプリ用APIプロジェクト: スマートフォンアプリに提供するAPI用プロジェクト(要件によって名前が変わる)
  lazy val appApi = Project(
    id = "app_api",
    base = file("app_api"),
    settings = serverSettings ++ ScalatraPlugin.scalatraSettings ++ Seq(
      parallelExecution in Test := false,
      libraryDependencies ++= Seq(
        // Scalatraなどに依存
      )
    )
  ) dependsOn(domain, jdbcImpl) enablePlugins JettyPlugin

  // 管理画面用APIプロジェクト: 管理画面ウェブアプリに提供するAPIプロジェクト(要件によって名前が変わる)
  lazy val adminApi = Project(
    id = "admin_api",
    base = file("admin_api"),
    settings = serverSettings ++ ScalatraPlugin.scalatraSettings ++ Seq(
      parallelExecution in Test := false,
      libraryDependencies ++= Seq(
        // Scalatraなどに依存
      )
    )
  ) dependsOn(domain, jdbcImpl) enablePlugins JettyPlugin

  // JDBC実装プロジェクト: ドメイン層のインタフェースをJDBCで実装したクラス群がまとまったプロジェクト
  lazy val jdbcImpl = Project(
    id = "jdbc_impl",
    base = file("jdbc_impl"),
    settings = serverSettings ++ Seq(
      parallelExecution in Test := false,
      libraryDependencies ++= Seq(
        // ScalikeJDBCなどに依存
      )
    )
  ) dependsOn domain

  // ドメインプロジェクト: ドメインモデルを表しているプロジェクト
  lazy val domain = Project(
    id = "domain",
    base = file("domain"),
    settings = serverSettings ++ Seq(
      libraryDependencies ++= Seq(
        // Joda-Timeなどの基本的な値を表現するライブラリのみに依存(フレームワークなどの詳細に依存しない)
      )
    )
  )
}

実際は serverSettings にscalac用のオプションを設定したり、sbt-scalariformを設定していたりします。もちろんプロジェクトの数も要件に合わせて増減します。

ですがコアはさきほどの project/build.scala のようになっており、ドメイン層をひとつのプロジェクトとして分離しているところが肝です。

サーバサイドにおいて、管理画面から登録したデータはアプリ側から参照され、アプリ側から記録したデータは管理画面で閲覧されるように、アプリ側APIと管理画面側APIの核となるビジネス要件、つまりドメインモデルは同一でないと都合が悪いです。そのため各APIごとにGitリポジトリを分けるといったことはしていません。以前1度だけそのように開発しましたが、各Gitリポジトリのドメインモデルがほとんど似たコードになってしまいました。

ではひとつのGitリポジトリかつひとつのプロジェクトで開発をするのか。いや、コードの規模が膨らむにつれて大変なことになるのが目に見えます。ですので、ひとつのGitリポジトリで、複数のプロジェクトに分ける。そして各ユースケース群(例えばアプリ側APIと管理画面側API)から共通して参照され、操作を行うドメインモデルはそれひとつでプロジェクトとして切り離す、という風にしました。

ちなみにドメインモデル以外にも共通で参照されるものがあります。それはドメインモデルの実装です。jdbcImplプロジェクトがそれに該当します。共通で参照されるものであればdomainプロジェクトに含めてしまっても、と考えるかもしれませんが、domainプロジェクトはドメインモデルの集合で、何を用いて永続化するのか、という詳細を知るべきではありません。よって各実装もそれぞれひとつのプロジェクトに切り離しています。jdbcImplはその例のひとつですね。

このように分けると、各プロジェクトがどのライブラリに依存するべきなのかはっきりしてきます。domainプロジェクトはさきほども述べた通り、詳細を知るべきではありません。よってdomainプロジェクトの依存ライブラリにScalatraやScalikeJDBCが含まれていたら危険信号というわけです。プロジェクトを分割することによって、こういった効果も生まれます。

アプリケーション層

それでは各層がどのように実装されているかコードで示していきます。ちなみにドメイン層はDDDの話が絡み、複雑になってくるので、今回触れずに次回触れてみようと思います。よってまずはアプリケーション層から。

package application.user

import domain.user._
import infrastructure.jdbc.user._

import scalikejdbc._

class UpdateUserNameCommand(val userId: UserId, val name: String) {
  // 引数のバリデーションを行う
}

object UpdateUserNameApplicationService {
  val userRepo: UserRepository[DBSession] = new JDBCUserRepository()

  def apply(command: UpdateUserNameCommand): User =
    DB localTx { implicit session =>
      val user = userRepo.find(command.uesrId)
      val nameUpdatedUser = user.updateName(command.name)
      userRepo.store(nameUpdatedUser)
      nameUpdatedUser
    }
}

アプリケーションサービスはドメインモデルを操作し、クライアントのユースケースを実現するためにあります。APIで言うと各APIひとつひとつがアプリケーションンサービスに該当します。上記は「ユーザーの名前を更新する」というユースケースを実現しています。また用いる実装の選択やトランザクションの制御もアプリケーション層が担います。よってdomainとinfrastructure.jdbc、そして実装の詳細(scalikejdbcが該当)をインポートしています。

ちょっと特殊なところはUpdateUserNameCommandというクラスでしょうか。これはプレゼンテーション層の処理を簡易にするためにあります。

プレゼンテーション層

ということでプレゼンテーション層です。各クライアントからの入力をアプリケーションサービスの入力に整え、アプリケーションサービスからの出力を各クライアントの出力に整える役割があります。

今回の話で言うと、APIはHTTPにて受け付けるので、入力および出力はすべてHTTPになります。弊社ではウェブアプリケーションフレームワークとしてScalatraを採用しています。よってHTTPの解釈に関してはScalatraに委ねており、入力および出力はすべてScalatraのオブジェクトをどうこうする形になります。

以下がScalatraからの入力をアプリケーションサービスの入力に変換する例です。

package presentation.user

import application.user._
import domain.user._

import org.scalatra._
import org.json4s._

object UpdateUserNameInbound {
  case class Body(userId: String, name: String)
  
  private implicit val jsonFormats: Formats = DefaultFormats
  
  def apply(context: ScalatraContextWrapper[ScalatraBase with JsonSupport[_]]): UpdateUserNameCommand = {
    val body = context.parsedBody.extract[Body]
    
    UpdateUserNameCommand(
      userId = UserId(body.userId),
      name   = body.name
    )
  }
}

ScalatraContextWrapperに関しては詳しい説明を割愛します。ざっくり言うと、Scalatraからの入力を1枚クラスで包んだものです。

大切なことはそのScalatraからの入力を解釈し、アプリケーションサービスが求める入力に変換する点です。これがもし別のウェブアプリケーションフレームワークを使うことになったらそれ用の変換を行うオブジェクトを追加してあげれば良い、ということになります。

次にアプリケーションサービスからの出力をScalatraの出力に変換する例です。Scalatraの出力、まあHTTPの出力になるわけですが、ActionResultというおあつらえ向きのものがあるので、それを出力とします。

package presentation.user

import application.user._
import domain.user._

import org.scalatra._

object UpdateUserNameOutbound {
  case class Body(userId: String, name: String)
  
  def apply(command: UpdateUserNameCommand, user: User): ActionResult =
    Ok {
      Body(
        userId = user.id.toString,
        name   = user.name
      )
    }
}

本来はアプリケーションサービス内で発生した例外のハンドリングも行うのですが、それについては省略しています。

入力と同様、大切なことはアプリケーションサービスからの出力をScalatraが求める出力に変換する点です。その点のみに終始した十分にシンプルなコードだと思います。

アプリケーション層と同様に、インポートしているパッケージに注目すると、application(と一部ドメインモデルのクラスを利用するためにdomain)とその詳細(org.scalatraやorg.json4s)の2種類だけです。決してinfrastructure.jdbcなどには依存しません。

これで:

  1. Scalatraからの入力をUpdateUserNameCommandに変換
  2. UpdateUserNameCommandをアプリケーションサービスが受け取り、Userを返す
  3. UserをScalatraへの出力に変換

という流れが出来ました。ですので:

package presentation

import presentation.user._
import application.user._

import org.scalatra._
import org.scalatra.json._

class UserServlet extends ScalatraServlet with JacksonJsonSupport {
  private implicit val formats: Formats = DefaultFormats
  
  before {
    contentType = formats("json")
  }
  
  post("/UpdateUserName") {
    val command = UpdateUserNameInbound(ScalatraContextWrapper(this))
    val user = UpdateUserNameApplicationService(command)
    UpdateUserNameOutbound(command, user)
  }
}

のようにScalatraのルールに則って書いてあげると動作します。

ちなみに実際のコードではこれと同じことを実現するために様々なインタフェースを定義したり、もっと簡便に書けるようになっています。

インフラストラクチャ層

インフラストラクチャ層はドメイン層のあらゆる詳細を実装する役割を担います。以下はdomain.user.UserRepository[X]のScalikeJDBC実装例です(UserTableやUserRecordは実際のテーブルと密接に結び付いたクラス)。

package infrastructure.jdbc.user

import domain.user._

class JDBCUserRepositroy extends UserRepository[DBSession] {
  def find(id: UserId)(implicit session: DBSession): User = {
    val u = UserTable.u
    
    val user = withSQL {
      select.from(UserTable as u).where.eq(u.id, id.value)
    }.map(UserRecord(u)).single.apply
    
    user getOrElse { throw new EntityNotFoundException(s"$userId is not found") }
  }
  
  def store(user: User)(implicit session: DBSession): Unit =
    if (exists(user.id)) {
      UserTable.update(user.id, user.name)
    }
    else {
      UserTable.insert(user.id, user.name)
    }

 def exists(id: UserId)(implicit session: DBSession): Boolean =
    try {
      find(id)
      true
    }
    catch {
      case _: EntityNotFoundException =>
        false
    }
}

うーん普通のコードですね。まあそれだけ驚きが少ないということで。

最後に

サーバサイドの事情としてAPIの細かい方針やらプロジェクトの概観をだらだらと述べてみました。実際のコードはもっと複雑ですし、核となるドメイン層のコードはまったく掲載していません。いやー実際のコードおもしろいんですけどね。でもまるまる掲載するとあれもこれも紹介したくなってしまい、2日目にしてやたらと長大な記事が上がってしまいます。うーん断念です。

ですので、次はドメイン層がどうなっているか紹介していければなと思います。それでは。

*1:私は常々ログイン/ログアウトを http://example.com/session へのPOST/DELETEで表現することに疑問がありました

非golangプログラマのためのghq入門 - Re.Ra.Ku アドベントカレンダー day 1

丸山です。今年もアドベントカレンダーの季節がやってきましたね。個人的にはScalaのアドベントカレンダーと、PostgreSQLのアドベントカレンダーを楽しみにしています。

弊社も技術ブログを持っているのだから、せっかくならばアドベントカレンダーをやろうじゃないか、というわけで、これから25日まで、毎日様々な記事が書かれていきます。

ScalaやAndroid Java,SwiftやJavaScriptなど、様々なプログラミング系の話題のほかにも、弊社らしくプログラマ向けのストレッチの話など健康にまつわる話題も飛び出す予定なので、ぜひ興味のある話題のときは読んでやってください。

さて、一発目のネタはghqです。

ghqは非golangプログラマ向けにも便利なリポジトリ管理ツール

ghqは、id:motemen さんが開発した、リモートリポジトリを便利にfetchしてきたり管理するためのコマンドラインツールです。ghqのrootディレクトリをGOPATHと同じにしてくと、ghqを利用してcloneしてきたリポジトリはgolangのディレクトリ命名規則に沿った場所に展開されます。そのため、golangを利用した開発を行っているプログラマにとってとても便利なツールです。

そのような背景から、「ghqってgolang向けのツールでしょ」というような「誤解」をたまに目にすることがありますが、ghqは非golang向けのプログラマにも十分に有用なツールです。

ghqのインストール、設定

homebrewでインストールできので、それでインストールしておくと楽です。

$ brew tap motemen/ghq
$ brew install ghq

次に、ghqのrootディレクトリを設定しておきましょう。ghqで管理するリポジトリはここで設定したディレクトリ以下に展開されることになります。わたしはGOPATHが~/devなので、それに合わせて~/dev/srcghq.rootに設定していますが、golang使ってないひとはどこでもかまわないと思います。

$ git config --global ghq.root ~/dev/src

ghqでリポジトリを取得する

$ ghq get <リモートリポジトリのlocation> 

とすることで、そのリポジトリがghq.root以下に展開されます。

たとえば、

$ ghq get git@github.com:rails/rails.git

とすれば、~/dev/github.com/rails/railsにrailsのリポジトリが展開されることになります。

ちなみに、getしてくるリポジトリのlocationはかなり柔軟に解決してくれて、railsの例でいえば

$ ghq get rails/rails

というように指定するだけでも同じく~/dev/github.com/rails/railsに展開してくれます。

ghq管理してるリポジトリに一発で飛ぶ

さて、これだけなら「べつにgit cloneでよくね?深いディレクトリ掘られると逆に面倒じゃね?」という感じになるのですが、ghqはpecoと組み合わせたときにこそ真価を発揮します。

pecoのインストールがなされていない場合、インストールしておきましょう。

$ brew install peco

さて、ここからpecoとghqを組み合わせて便利にする時間です。zshをお使いの場合は、以下のような記述を.zshrcなどにしてみてください。

function peco-ghq-cd () {
    local selected_dir=$(ghq list | peco --query "$LBUFFER")
    if [ -n "$selected_dir" ]; then
        selected_dir="`ghq root`/$selected_dir"
        BUFFER="cd ${selected_dir}"
    fi
    zle clear-screen
}
zle -N peco-ghq-cd
bindkey '^f' peco-ghq-cd

この状態でターミナルを立ち上げなおし、Ctrl+fを押すと、ghq get で取得したリポジトリが一覧でできます。飛びたいリポジトリを選択すると、ターミナル上のバッファにcd <選択したディレクトリのパス>が表示されているはずです。これで、リポジトリ間を移動するときにいちいち ls して cd してみたいなことをせずにすむようになりました!

こうやって記事で書かれてしまうと「なんだかあんまり便利に見えないなあ」なんて思うかもしれませんが、一度ghqのある生活に慣れてしまうともうghqのない生活に戻りたくないくらいに便利なので、ぜひみなさん試してみてください。

参考にしたURL

React + ReduxではないSPAフロントエンド事情

丸山です。

弊社も最近はiOSアプリやAndroidアプリだけではなく、いわゆるSPAなWebApplicationを開発しています。

SPAを採用した背景

その背景には

  • サーバーサイドで複雑なビジネスロジックを実装することになるので、サーバーサイドはScalaを利用したい
  • ScalaでHTMLを吐いてもいいのだけれど、そういうのは動的型付け言語のほうがやっぱり楽な気がする
  • だったらScalaでJSONを喋るAPIだけ作ろう
  • そのAPIと喋ってwebのユーザーインターフェイスを実現する部分はRoRにするか?
  • それともいっそSPAにしてブラウザから直接APIとコミュニケーションすればよくないか?

と、いろいろ考えたところで、まずは社内向けのツールで技術検証も兼ねてSPAでScalaなAPIとコミュニケーションするのを試してみることにしました。

まだまだ開発中なのですが、意外とうまくいっているな、というのが現在の感想です。

採用したフレームワーク

最近SPAといえばだいたいReact + Reduxというのがトレンドであるように思います。しかし弊社ではVue.jsとvue-routerを利用しています。

これには、まだまだ開発メンバーの少ない弊社では、iOSアプリケーションやAndroidアプリケーションを開発しているメンバーがSPAアプリも開発するというようなことが多発することが予測できることが影響しています。

弊社ではiOSアプリの設計についてはだいぶ方向性が見えてきていて、その内容はこの前のヤパチーでも発表させていただきました。

techblog.reraku.co.jp

上述の発表内容を見ていただければわかると思いますが、弊社ではかなり古典的なMVWな設計でアプリケーションを実装しています。

さて、上述の通り、弊社には「JavaScriptバリバリ専任マン」のような開発者はおらず、iOSアプリなどを書いている開発メンバーがJavaScriptも書くことになります。そのようなビジネス上の制約があるとき、「なるべくiOSやAndroidで使っていた考え方と同じような考え方でSPAも開発することができる」というのは無視できないメリットです。そのため、「なるべく普通のMVW」が実現できて、なおかつModelの設計については何も強制してこないフレームワークが必要になったわけです。そのようなことを考えた結果白羽の矢が立ったのがVue.jsでした。今の所この選択は間違えていなかったな、と思っています。

Vue.jsを利用したSPAフロントエンド開発事情

使っている技術スタックとしては、JSまわりにVue.js、vue-router、Babel、CSSはsass、これらを webpack でビルドして使っています。

タスクランナー的なものは採用しておらず、npm scriptを利用しています。package.jsonの一部を抜粋するとこんな感じ。

  "scripts": {
    "build": "npm run build_js && npm run build_html",
    "build_js": "webpack --progress",
    "build_html": "cp src/index.html build/index.html",

    "watch": "npm run build_html && webpack -d --watch --progress",

    "clean": "npm run clean_js && npm run clean_html",
    "clean_js": "rm -rf build/js/*",
    "clean_html": "rm -rf build/index.html",

    "test": "mocha-webpack --colors --webpack-config webpack.config-test.js \"test/**/*.js\""
  },

タスクランナーを利用しないという選択にも「専任のJavaScriptマンがいない」ということが影響していて、「なるべく覚えなければいけないことを減らそう」という判断の結果このような運用になっています。「ビルドツールがCLIインターフェースを持ってるならそれをshellから叩いてしまえばいいではないか」というのはまあ自然な発想という気はします。

テスト事情

PDSを意識してアプリケーションの動きをModelレイヤーにどんどん寄せて書いているので、UIテストは今の所行っていません。nodeを利用してApplicationServiceのテスト(あとDomainModelのテストも)をすればそれでだいたい事足りています。

というのも、ヤパチーで発表した連打マシンと同じような感じの設計で、UI上の状態もほとんどAppliactionServiceが保持するModelたちが持っていて、その状態を書き換えたいときはApplicationServiceのメソッドをdispatchする、そしてその結果状態が書き換わったらObserver経由でUIに反映する、という一方向のデータフローを守っているので、ApplicationServiceへのテストがE2Eテストに近い役割を担っていてくれている感じです。

実際Vue.jsに依存するコードはプロジェクトのうちごく一部だけとなっているため、今の所このやり方でも大きな問題は出ていません。リファクタも快適に行えています。

ただ、どこかでUIテストのノウハウとReduxのノウハウもきちんと得ておきたいところではあります。このあたりは今後の課題ですね。

まとめ

弊社で最近行っているSPA開発事情について、「こんな感じだよ」というのを書いてみました。

最後に「また宣伝かよ!」という感じでアレなんですが、株式会社リラクでは「一晩でReduxに書き換えてやるぜ!むしろ俺がReduxのメリットの伝道師になってやる!」という気概のある開発者や、あるいは「これはこれで合理的な選択!一緒に開発していきたい!」という気持ちを持ってくれる開発者、あるいは「まだ自信はないけど、設計についての話ができる仲間たちといろいろ学びつつやっていきたい!」と思ってくれるような開発者を募集しています。マジで。ほんとに無限にやることがあるんです。助けてくれ!少しでも興味があるひとは是非 @ が誰からでもDMを受け付けるようになっているので気軽にコンタクトを取ってください。ほんとに待ってます。

YAP(achimon)C::Asia Hachioji リラク参加の御礼

こんにちは。ヘルステックチームの泉原です。

YAP(achimon)C::Asia Hachioji
リラクも、スタッフとしてお力添えさせていただきました。

また、ヘルステックチームの丸山・近藤は、トークでも参加させていただいております。

参加メンバーは各自、ブログで感想を書いています。
丸山ブログ
近藤ブログ
泉原ブログ

丸山・近藤は、ブログ内でトーク内容も公開していますので、
今回参加できなかった方も、トーク内容が閲覧できます。
ぜひご確認くださいませ!

今回主催のuzullaさん
会場提供のMicrosoftさん スタッフさん、参加者の皆様
皆様本当にありがとうございました!!

YAP(achimon)C::Asia Hachiojiに参加しました

こんにちは。ヘルステックチームの泉原です。

先日こちらのブログで報告した通り、ヤパチーにスタッフとして参加させていただきました。
http://yapcasia8oji-2016mid.hachiojipm.org/

初参加で、非プログラマの泉原ですが、とっっっても楽しかったです。

登壇した方も、見に来る方も、皆さん本当に純粋にプログラミングが好きな方々で、
話の内容が分からなくても熱意を感じるだけでも凄く楽しい場でした。
また、やはりプログラマーの方の話は構造化されていて、とっても分かりやすい!
知識がなくても説明が分かりやすいので、ついつい引き込まれて話を聞いてしまいます。

プロダクト管理やハードウェアの話、お肉の話など・・。
プログラムを愛する人たちから、プログラミング以外のトークも聞けるイベントです。
実際にコードが書けなくても、プログラミングが好きな方は、次回以降ぜひ参加してみていただきたいです。

ボランティアで開催してくれた主催の方々、スタッフの方々、登壇された方々、参加してくださった方々、
皆様のおかげでとても楽しい2日間を過ごすことができました。
本当にありがとうございます!

次回は私も「プログラマ向けセルフストレッチ」という内容で、LT参加表明しようと目論んでいます。
次回も楽しみです!

#yapc8oji 得票数4位トーク「あの日見たM V WhateverのModelを僕たちはまだ知らない」実況中継

丸山です。ヤパチー、最高に楽しかったですね。スライドの公開はしたのですが、正直重要なことは全部口頭で説明していて、スライドには情報が少ない。しかし、動画を見るのは正直だるい。40分もPCの前で集中して映像を観れないですよね。知ってる。というわけで、受験参考書の「実況中継」シリーズ(知ってます?)方式で、プレゼンをブログで再現します。長いぞ。でも多分動画見るよりははやく読み終われる。ぜひ読んでいってください。

アバンパート

f:id:nkgt_chkonk:20160706013649j:plain

今日はこういうトークをします。

f:id:nkgt_chkonk:20160706013650j:plain

簡単に自己紹介すると、こういうものです。

f:id:nkgt_chkonk:20160706013651j:plain

息子紹介です。かわいいですね。

f:id:nkgt_chkonk:20160706013652j:plain

WEB+DB Press 91号では特集を、92号ではPerlHackersHubに記事を書かせてもらいました。もう原稿料いただいてるので、もうこれ買ってもらってもわたしには一銭も入らないんですけど、いい雑誌なので買ってないひとはぜひ買って読んでください。

f:id:nkgt_chkonk:20160706013653j:plain

まずMVWってなに?って話からしていきたいんですけど、

f:id:nkgt_chkonk:20160706013654j:plain

MVCとかMVPとかMVVMとかみなさん聞いたことがあると思います。これらを総称してM V Whateverと呼んだりします。

f:id:nkgt_chkonk:20160706013655j:plain

f:id:nkgt_chkonk:20160706013656j:plain

違う名前ってことはそれぞれに違いがある一方、MVWとひとくくりにされるのだから共通点もあるわけですね。

共通点としては、これらは全部PresentationDomainSeparation(以下PDS)を実現するためのパターンである、ということが挙げられます。一方、それをどうやって実現するのか、という部分についてはそれぞれに違いがあります。しかし今回はその違いについては立ち入りません。

今さらっと「PDS」といいましたが、ではそもそもPDSとはなんでしょうか?

f:id:nkgt_chkonk:20160706013657j:plain

Martin Fowler先生が書いてるんですけど、

f:id:nkgt_chkonk:20160706013658j:plain

f:id:nkgt_chkonk:20160706013659j:plain

要するにプレゼンテーションとドメインの分離ですね。

「いやそれじゃあそのままじゃん」って感じなんですけど、プレゼンテーションとドメインとはなにかの前に、

f:id:nkgt_chkonk:20160706013700j:plain

Martin Fowler先生が「PDSってのはMVCの最も重要パートなんだよ」と言っていることを確認しておきましょう。そう、MVCはPDSと切っても切れない関係である、ということをここでは確認しておきましょう。

で、じゃあプレゼンテーションとドメインってなに?って話になるんですけど、まあプレゼンテーションはなんとなくイメージがつきそうですね。ユーザーが見れて触れる部分、みたいな感じだよな、って思いませんか。まあだいたいそんなもんです。

f:id:nkgt_chkonk:20160706013701j:plain

一方、ドメインって聞くと、「あっDDDでやったところだ!」ってなりがちなんですけど、PDSの文脈におけるDomainとDDDにおけるDomainってのはたまたま名前が同じなだけで、指してるものは違います。

f:id:nkgt_chkonk:20160706013702j:plain

じゃあPDSにおけるドメインってなに?って話になるんですけど、

f:id:nkgt_chkonk:20160706013703j:plain

MVWのModelに相当するのがドメインです。

f:id:nkgt_chkonk:20160706013704j:plain

とはいえですね、これって言葉を言い換えただけで、「じゃあMVWにおけるモデルってなんなんだよ」という問題は依然として残るわけですね。

f:id:nkgt_chkonk:20160706013705j:plain

よく言われる説明としてはこんなのがありますね。ただですね、

f:id:nkgt_chkonk:20160706013706j:plain

そうすると「ビジネスロジックって結局なんなのだ、宇宙とは、人生とは」みたいな哲学に迷い込むわけです。この会場で「ビジネスロジック」の定義言えるひとっています?……あ、いないですよね。わたしも言えません。

f:id:nkgt_chkonk:20160706013707j:plain

死って感じですね。お疲れ様でした。

でもこれみなさんが悪いわけじゃないと思うんですよ。そもそもビジネスロジックということばで指してるものがみんな違ったりするんですよね。そんな状態で、「ビジネスロジック」って言葉を使っちゃって名前がついちゃうと「なんだかわかったつもり」になったまま実はなにもわかってない、みたいなことになりがちなんですよね。もうだから今日は「ビジネスロジック」って言葉忘れちゃった方がいいと思います。

何が言いたいかっていうと、「モデルとは何か」ってのを言い表すのはすごい難しいんですよ。

f:id:nkgt_chkonk:20160706013708j:plain

というわけでですね、わからないときは分かりやすいところから考えましょう。

f:id:nkgt_chkonk:20160706013709j:plain

まあViewはわかりやすいですよね。iOSだったらUIViewがあるし、AndroidにもViewがある。webAppだったらHTMLとCSSで表現されるのがViewですよね。

f:id:nkgt_chkonk:20160706013710j:plain

実はWの部分もわかりやすくて、こういうやつらです。これらが何を担っているかっていうと、ViewをレンダリングしたりViewに値突っ込んだり、あるいはユーザーからの入力を受け取るみたいなこともやりますよね。

f:id:nkgt_chkonk:20160706013711j:plain

で、PDSに立ち返ってみると、ファウラー先生は「(the user interface)」っていってますよね。UIって見た目だけじゃなくてユーザーがボタン押したら云々みたいなユーザーインタラクションも含むものだってのは開発者のみなさんはよくご存知だと思うんですけど、それと照らし合わせて考えると、

f:id:nkgt_chkonk:20160706013712j:plain

VとWの担ってる責務こそが「プレゼンテーション」であるってことなんですよね。で、VとWでプレゼンテーションを実現するわけです。で、WにはCとかPとかVMとかいろんなものが入ってくるんですけど、これは要するに「どうやってプレゼンテーション層を書くか」のやり方をいろいろパターン化してくれてるわけですね。

f:id:nkgt_chkonk:20160706013713j:plain

じゃあ残りの、MVWでいうところModel、PDSでいうところのDomainっていうのはどんな責務を担当するんだろう、って話についに切り込んでいきます。

f:id:nkgt_chkonk:20160706013714j:plain

PDSにみたび登場してもらうとですね、赤字で書いたところ注目してください。「the rest」って書いてありますよね。

f:id:nkgt_chkonk:20160706013715j:plain

まあこういうことです。

f:id:nkgt_chkonk:20160706013716j:plain

ちーん、結局なんなのかわかんない!って感じですよね。

でもそれは当たり前なんですよ。そもそもPDSってのは、「プレゼンテーション層とそのほかを分けよう」って考え方だし、この「その他」が何を担うべきなのかってのはアプリケーションによって変わるわけです。つまり、これ大事なことなので何度も強調しますが、

f:id:nkgt_chkonk:20160706013717j:plain

そもそもMVWは「プレゼンテーション層をどう設計するか」のパターンであって、Modelをどう設計するべきかについては何も語っていないパターンなんですね。

f:id:nkgt_chkonk:20160706013718j:plain

はい、ここまでがアニメとかでいうところのアバンパートです。というわけでオープニング、タイトルコールどん!

Aパート

f:id:nkgt_chkonk:20160706013719j:plain

はい、こういうタイトルでしたね。MVWはModelの設計指針についてなにも語っていないことは確認できました。そうすると次の疑問として「じゃあModelはどうすんのよ、なんの指針もなしにどうやって設計すんの」って話が出てきます。そういうときに力になるのが様々なデザインパターンです。デザインって設計のことですよね。そういうパターンをたくさん知ることで、「ははーんこれはxxパターンを適用すべき事案ですね?」とか見えてくるようになるわけです。

f:id:nkgt_chkonk:20160706013720j:plain

今日はそんな中でみっつほどパターンを紹介しましょう。

f:id:nkgt_chkonk:20160706013721j:plain

まずはActiveRecordパターンです。これRailsのおかげでみなさんも結構よく知ってると思うんですけど、

f:id:nkgt_chkonk:20160706013722j:plain

こういうパターンですよね。

ただ、Railsで使われてるActiveRecordライブラリを使えば自動的にARパターンが実現できるわけではないんですよ。

f:id:nkgt_chkonk:20160706013723j:plain

たとえばこういうコード見てみましょう。Controller で AR::Base 継承したNotificationから、unreadなものを引いてきて、それを全部readに更新してます。いわゆる「mark all as read」ってやつですね。

ところで、ControllerってPDSでいうとPとDどっちに属すやつでしたっけ?プレゼンテーション層ですよね。でも、この「DBからレコード引いてきて更新する」って、全然ユーザーインタラクションじゃないですよね。そこで、ARパターンを適用してみます。

f:id:nkgt_chkonk:20160706013724j:plain

はい、テーブルに対する操作なのでクラスメソッドとして実装しました。いいですね。(あっ、これActiveRecord::Base継承しわすれてますね、してると思って読んでください)

注目すべきはcontrollerです。これ、ユーザーからリクエスト、つまり入力を受け取って、Modelに「あとはまかせた!」ってやってますよね。これはユーザインタラクションの実装で、プレゼンテーション層の責務の範囲に収まってると言っていいと思います。

f:id:nkgt_chkonk:20160706013725j:plain

こうしたことによって、なにが嬉しいかというとですね、実はプレゼンテーション層ってのはだいたいテストしにくいんですよね。RailsだったらRack経由でテストしないとダメだし、iOSアプリとかだとViewとかViewControllerが絡むと一気にテストしにくくなりますよね。でも、Cの責務をユーザーインタラクションだけに絞ったことで、あとはRackとかそういうテストしにくいところ経由しないでテストが書けるようになりました。嬉しいですね。

あと、もう一個いいことがあります。Cにいろいろ書かれちゃうとそこ再利用するの難しいんですけど、こうやってmodelに書いておけば、controllerからだけじゃなくてrakeタスクとかからも呼べるようになりますよね。これって、「プレゼンテーション層を入れ替えてる」ってことなんですよ。このアプリケーションを使うクライアントからみると、RailsのViewとかControllerで実現してるのってHTTPを喋るプレゼンテーション層なんですよね。一方で、CLIのインターフェイスを実現するrakeタスクなんかからこのModelを使えば、rakeタスクの層が「CLIを喋るプレゼンテーション層」になるってわけです。こうやってプレゼンテーション層をぽこぽこ入れ替えることができるのも、PDSに沿うことで得られる強みですね。

f:id:nkgt_chkonk:20160706013726j:plain

とはいえ、現実は厳しくて、「これでめでたしめでたしやで!」とはなかなか行かないんですね。よくある課題としては、複数のテーブル触るようなユースケースをどっちに書くのか問題ってのがありますよね。あるいは、ARを継承したモデルの中でトランザクション開始しちゃうと、そのメソッドをほかのところからも使おうとすると多重にトランザクションが開始されちゃってうわあみたいな感じになったり、みなさんも経験ありませんか?

そういうのむりしてActiveRecordパターンで扱おうとしていろんなところにぐちゃーっと詰め込んでいくと、

f:id:nkgt_chkonk:20160706013727j:plain

まあメンテ不可能なRailsアプリが出来上がるわけですね。はい、お疲れ様でした。

f:id:nkgt_chkonk:20160706013728j:plain

じゃあ、何がいけなかったのか考えてみましょう。そもそもActiveRecordパターンってのはどんなパターンでしたっけ?「テーブルと」クラスが1:1なんですよね。そもそもテーブルとクラスが1:1なんだから、複数テーブルにまたがった関心を扱うには「食い合わせのわるい」パターンであるってことが言えるんじゃないでしょうか。実際、実践DDDっていう最高の本があるんですけど、この中でも「単なるCRUDアプリならRailsでいいかもね」って言ってて、要するにActiveRecordパターンが対象にするのってCRUDの範囲だけなんですよね。つまり、

f:id:nkgt_chkonk:20160706013729j:plain

ARパターンだけでこの世のすべての問題を解決しよう、というのは無理があるわけです。

f:id:nkgt_chkonk:20160706013730j:plain

ちなみになんですけど、RubyのARライブラリってARパターンをもとにしたライブラリなんですけど、単なるARパターンじゃなくてもっといろんなことやってくれるんですよね。だから、RoRの界隈ではARライブラリベースでやっていこうという考え方もあるにはあるっぽいんですけど、あまりこれでうまくいったという話を聞かないような気がします。逆にARライブラリベースで上記みたいな問題を解決したぜっていうのは興味ある話なので、そういう話知ってる人はぜひ教えてください。皮肉とかじゃなくてまじで聞きたいです。

f:id:nkgt_chkonk:20160706013731j:plain

ええと、話を戻して、ARパターンですべての問題が解決できるわけではないということを見てきました。そこで、別のパターンとしてTransactionScript(以下TS)ってのを見てみましょう。

f:id:nkgt_chkonk:20160706013732j:plain

TSってのはユースケースと一対一になるメソッド定義して、そのメソッドの中に全部手続きを書いていくやつです。

たとえば、航空機の座席予約について考えてみましょう。まず、

  • その席が誰かに先に予約されてないかチェックして
  • ビジネスクラスの料金でファーストクラス予約しようとしてないかチェックして
  • 場合によってはパスポートのvalidityチェックして

とか、いろいろとやるべきことはあるわけです。それを全部上から下に書き下していくスタイルですね。

これを適用すると、こんな感じのコードが出来上がってきます。

f:id:nkgt_chkonk:20160706013733j:plain

いろんなモデルを使って、ユースケースをがんがん書いていくわけですね。トランザクションもこのメソッドの中で開始してcommitすればいいです。

ちなみに、これはTSとARの合わせ技になってますね。

ここでもcontrollerに注目してください。入力を受け取ってTSのメソッドをdispatchしてるだけです。TransactionSctiptパターンで作ったこのNyanTransactionクラスもModelの一種であることに気をつけて下さいね。再度確認しますが、Modelというのは「プレゼンテーション以外全部」なのだから、当然ARを継承してないこのNyanTransactionもModel層のものです。だから、この場合もCは入力を受け取ってMの処理をdispatchしてるだけなわけです。

f:id:nkgt_chkonk:20160706013734j:plain

で、こういうふうにすると何がうれしいのかって話なんですけど、ARだけでは実現できてなかった複数の関心をうまくまとめられてるし、トランザクションもここに書いておけばバッチリ良さそうですね。

f:id:nkgt_chkonk:20160706013735j:plain

とはいえやっぱり現実は厳しくて、まあ一定以上複雑なアプリケーションをTSパターンでとこうと思うと、似たようなロジックがあちこちにコピペされて死んだり、ひとつのメソッドの行数数えたら10億行あってそれ読むのに5億年かかるしもはやテスト不能みたいな状態になるんですよね。

f:id:nkgt_chkonk:20160706013736j:plain

はいお疲れ様でした、という感じです。

f:id:nkgt_chkonk:20160706013737j:plain

とはいえですね、そこまで複雑でないアプリケーションならば、TSってすごく見通しがいいんですよ。どっかがバグったら、そのユースケースと1:1になってるメソッド読みに行けばいいって一瞬でわかるし、そもそもそのメソッドも「あれしてこれしてそれする」ってことしか書いてないから、複雑じゃなかったらむしろ読みやすいんですよ。あと、余計な設計しないで済むんで、初期開発速度も速いわけです。

そうなってくると、たとえば「とにかくバギーでもいいから早くリリースしてくれ!そんである程度までユーザー数伸びたらどっかに売ってあとメンテしてもらおう!そんで遊んでくらそう!」みたいなビジネス要請がある場合にはまあ倫理的にどうかって問題はあるにせよ、TSでドバーって書いちゃうほうがよかったりするんですよね。一方で、「複雑な内容だから急がないんだけど、ずっと直しながら使っていくシステムをしっかり作ってほしい」っていうようなやつをTSで設計したらちょっとまずいわけですよね。

そんな感じで、モデルをどう設計するのかってのは実は「どういうビジネス要請があるのか」みたいなところと深く関わっているわけですよ。だから、モデルを設計するときには各パターンのメリットデメリットを知って、そしてそのパターンが何を解決しようとしているのかを知って、その上で「こういう要請があるから今回はこのパターンを使っていこう」というように設計を進めていくべきなんですね。

だから「TSはクソ、時代は○○」みたいな話ではないんだよ、ってのを強調しておこうと思います。

f:id:nkgt_chkonk:20160706013738j:plain

まあとはいえですね、TSが適さない問題領域、ビジネス要請っていうのはあるわけですよね。複雑でなおかつ堅牢でなければならないみたいな。

そういう領域を解くときには、Layered Architcture(以下LA)を検討してみたらどうでしょうか。

f:id:nkgt_chkonk:20160706013739j:plain

どういうものかっていうと、こんな感じです。プレゼンテーション層はいいですよね、その下が全部モデルになるわけですけど、この層を、みっつに分けます、まずはCがdiapatchする窓口となるApplicationServiceっていう層があって、あとDBアクセスとかそういう汎用的な技術を実現するレイヤーをInfrastructureって定義して、それ以外をすべてDomainModelという層にやってもらう。そういうパターンです。

これ何がいいかっていうと、InfrastructureがDBアクセスとかやってくれるから、DomainModelはほんとに解きたい問題に集中できるんですよ。

たとえば、タスク管理システムを考えてみましょう。

f:id:nkgt_chkonk:20160706013740j:plain

そのタスク管理システムは、一回だれかがアサインされたタスクに関しては二度と「だれもアサインされた状態」には戻せないってことにしましょう。これはマネージャーはだれかをアサインしたと思って安心してたらだれかそれをさし戻しちゃって、誰も誰がボールを持っているのかわかんない宙ぶらりんのままのタスクができるのを避けるためです。あと、一回クローズされたらもう二度とオープンできないようにしちゃいましょう。「一回クローズしたのは終わったんだからそれは終わったものとして扱う!!!再オープンしたい?それは修正ではなくて変更です!新しいチケットを切ってください!」ってことですね。

まあそういう「このアプリケーションが解きたい問題領域」ってのをDomainModelにOOでコードでモデリングしていきます。

f:id:nkgt_chkonk:20160706013741j:plain

そんで、さっき定義したTaskStatusを利用するTaskクラスをこんな感じで定義してみましょう。

このシステムではタスクはマネージャしかオープンできません。権威主義的で最悪のサービスですけど、まあ例として許してください。そういう知識をTask.openメソッドの中にこうやって書いていきます。あとはまあ普通にオブジェクト作って返してますね。

あるいは、Taskをだれかにアサインするメソッドみてください。やっぱり権威主義的で最悪のサービスなんですけど、だれかにタスクをアサインできるのはマネージャだけです。あと、Taskの状態遷移はできる遷移とできない遷移がありましたよね?それをモデリングしたTaskStatusを利用して、「ちゃんとこれは許された遷移かな?」とかをチェックしたりしてます。

ここで注目してほしいのは、TaskとかTaskStautsみたいなDomainModelに定義されてるクラスは、自分がどこに永続化されてるとか気にしてないことです。そういう技術的な詳細を避け、「複雑な問題」に集中して丁寧にOOで設計して責務分割していくわけです。

f:id:nkgt_chkonk:20160706013742j:plain

そうやって丁寧に責務分割したとしても、オブジェクトがメモリに乗ってるだけではまああまり意味ないですね。永続化はどうすんの?という話があります。今回はリポジトリーパターンってのを使ってみましょう。リポジトリパターンってのはどういうやつかっていうと、なんちゃらリポジトリにDomainModelで定義したなんらかのオブジェクトを渡して永続化してもらったり、永続化されてるところから情報引っ張ってきてそれを使ってDomainModelで定義したクラスのインスタンスを返してくれたりするようにするパターンです。今回だったらTaskをRDBに保存するTaskRepositoryってのを定義してみました。

f:id:nkgt_chkonk:20160706013743j:plain

で、ApplicationServiceがDomainModelとInfrastructureを利用して、ユースケースを実現します。ここに定義したメソッドがControllerとかからdispatchされるわけですね。

f:id:nkgt_chkonk:20160706013744j:plain

こういうパターンを利用することで、TSのときは一枚岩でメンテ不可能だったような複雑な問題を適切に分割して統治することができそうです。

一枚岩のスクリプトをテストするのは非常に困難ですが、今回はDomainModelをきちんと設計しているので、単体テストも非常にしやすいですね。

あと、DBアクセスをInfrastructureに追いやったことで、複雑な問題を解く部分、すなわちDomainModelの層はDBなしでテストできるようになりました。最高ですね。

f:id:nkgt_chkonk:20160706013745j:plain

とはいえやっぱり銀の弾丸はないわけで、これって非常に難しいんですよね。そもそもLAに慣れてないプログラマにとっては「これどの層に書けばいいの?ApplicationService?DomainModel?」みたいな話になりがちです。

まあそれだけならともかく、OOって本質的に難しいと思うんですよ。さっき私が書いたTaskStatusとTaskの責務分割もほんとにあれでよかったんですかね?遷移先を渡すとそこに遷移できるかどうかをboolで返すんじゃなくて、TaskStatus#transit_to_assignedみたいなメソッドにして、そいつが繊維先の状態であるTask::Assingedのインスタンス返すようにする。そんで、もし行けないステータスに行こうとしてたら例外吐くみたいなほうが「チェック忘れ」とかなくなるしいい設計なのでは?

そんなわけで、とにかくやっぱりOOって難しいんですよ。そうするとドメイン層に関してはプログラマとしてのレベルを上げて物理で殴るとか、あるいは複雑な問題領域に対する深い理解とかが必要になってくるんですよね。

f:id:nkgt_chkonk:20160706013746j:plain

ほんとにつらいですね。はい、お疲れ様でした。

f:id:nkgt_chkonk:20160706013747j:plain

とはいえですよ、われわれはもともと「複雑な問題」に立ち向かうためにここまでやってきたわけです。そもそも難しい問題を相手にしているのだから、そこはがんばって、歯を食いしばってやっていくしかないんですよね。こういう複雑な問題を解くときにこそ、OOへの深い理解とか、あるいはDDDへの理解みたいなのが役にたつのだと私は思います。やっていきましょう。

f:id:nkgt_chkonk:20160706013748j:plain

ちなみにFAQなんですけど、聞きかじりでこういう話を「これって要するにDDDでしょ?」っていう向きもあるんですけど、DDDとLAは同一視しないほうがいいです。DDDの中でLAに触れることはありますが、DDDはそれだけではなくてもっといろんな概念を含んだ、より広い概念です。むしろLAはおまけというか、DDDを生かすための前提みたいなところがあると思います。単なるLAをDDDと呼んだりするとDDDの怖いひとにはてブとかTwitterで怒られるみたいな光景をよく目にしますので、気をつけてやっていきましょう。

f:id:nkgt_chkonk:20160706013749j:plain

はい、ようやくAパート終了です。というわけでアイキャッチどん!

f:id:nkgt_chkonk:20160706013750j:plain

あー、かわいいですね。

Bパート前のアイキャッチもどん!

f:id:nkgt_chkonk:20160706013751j:plain

あー、かわいいですね。不審なお兄さんもいますね。

Bパート

f:id:nkgt_chkonk:20160706013752j:plain

はい、つづいてBパートです。Aパート結構概念的な話多かったんで、GUIアプリを例に実際になにかを作ってみましょう。

f:id:nkgt_chkonk:20160706013753j:plain

こういう連打マシーンはどうでしょう。ボタンをタップすればカウンタが1進んでいくのでガンガン連打してくれ!!ってやつです。ただひとりでやってても面白くないので、ネットワーク越しにいろんな人が同時に連打します。で、手元のカウンタには自分の連打数じゃなくて、「みんなでどれだけ連打したか」が表示されます。

f:id:nkgt_chkonk:20160706013754j:plain

ちょっと実装方針を考えてみますが、ボタンを一回タップしたごとにHTTPリクエストとか飛んだらまじで目も当てられないという感じなので、クライアント側では連打をバッファリングしておいて、一定時間ごとにそれをflushするようにしましょう。

とはいえ、タップした瞬間にカウンタが動かないとUXとしては最悪なんで、タップすると、「実際にはまだサーバに送ってないんだけど、見た目の都合でカウンタは1増える」というようにしましょう。

あとは、サーバーから定期的に「現在のみんなの総連打数はこれだよ」って送られてきて、それを受け取ったらカウンタをその数字まで進める、という実装方針でいきましょう。

f:id:nkgt_chkonk:20160706013755j:plain

単なる連打マシンなんですけど、実装がちょっと複雑になりそうですね。そもそもRDB関係ないアプリなんでARパターンはマッチしなそうです。バッファリングとかいろいろあるからTSもマッチしなそうですね。LAっぽく考えてみましょう。

f:id:nkgt_chkonk:20160706013756j:plain

まずはModelを考えていきます。

f:id:nkgt_chkonk:20160706013757j:plain

なにはともあれ、カウンターを表すクラスが必要ですよね。タップされたときは1増えるのでincrementメソッド生やしておきましょう。あと、サーバーから数字送られてきたらそこまでいっきに飛ばないとだめなんで、setCountメソッドも生やしておきましょう。こいつらが内部に保持したcountを操作します。

f:id:nkgt_chkonk:20160706013758j:plain

はい、こんどはタップ数をバッファしてくれる君です。タップされたらカウントをインクリメントして、flushメソッドが呼ばれたらflushします。今回はプレゼンの時間の都合上サーバーにアクセスする部分は省略しますが、本来ならもっと真面目に書かないとダメです。

f:id:nkgt_chkonk:20160706013759j:plain

で、あとはサーバーから送られてくる値をさっき作ったカウンターにセットしてあげる君ですね。connectToServerすると、一定時間おきにサーバーから値が送られてきて、それをcounterにセットしてます。

これもプレゼンの時間の都合上バッサリ実装カットしてますが、もっとまじめに書かないと本来はダメです。

f:id:nkgt_chkonk:20160706013800j:plain

さて、あとは、これらを束ねてユースケースを実現するApplicationServiceを書きましょう。

プレゼンテーション層からconnectToServerが呼ばれたらfetcherを起動します。起動しておけばあとは勝手にcounterがアップデートされます。これはコンストラクタに書いてもいいかもですね。

ボタンがタップされたらこのtapメソッドを呼びます。そうすれば、カウンタは1増えるしバッファもされます。

あとは、プレゼンテーション層が一定時間おきにflushBufferメソッドを呼んであげれば、サーバーに値が送信されます。

f:id:nkgt_chkonk:20160706013801j:plain

さて、これでアプリケーションの挙動はモデリングできました。

f:id:nkgt_chkonk:20160706013802j:plain

こんどはプレゼンテーション層を書いていきましょう。

f:id:nkgt_chkonk:20160706013803j:plain

こんな感じで、Viewがロードされたら、さっき作ったapplicationServiceをセットアップします。あとはタイマーイベント仕込んで、定期的にapplicationServiceのメソッドを叩いてあげます。また、tapされたというイベントが起こったらapplicationServiceのtapメソッドをdispatchしてあげましょう。

タイマーイベントってここに書くの?という疑問も上がりそうですが、タイマーってアプリケーションに対する一種の入力だと考えることができると思いませんか?そう考えるとPresentation層にあるのが自然な気がしますね。あとこのほうがModelのテストしやすい。

f:id:nkgt_chkonk:20160706013804j:plain

まあこんな感じで、プレゼンテーションからイベントを受け取ってapplicaitonServieをinvokeするところまでできました。

f:id:nkgt_chkonk:20160706013805j:plain

ただですね、これひとつ問題があって、これだけだとタップしてもタイマーイベントが走ってもサーバーから値が降ってきても、Viewが描き変わらないんですね。内部でModelの状態が変わりまくっていくだけで、画面が一切動かない。これはあかんですね。

f:id:nkgt_chkonk:20160706013806j:plain

で、よく考えると、今回はCounterモデルの値が書き換わったときに、それに合わせてUILabelも書き換えればそれだけでよさそうです。

f:id:nkgt_chkonk:20160706013807j:plain

f:id:nkgt_chkonk:20160706013808j:plain

なんかの状態が変わったことを検知して、なにかをしたい。

f:id:nkgt_chkonk:20160706013809j:plain

Observerパターンでやったところですね!

f:id:nkgt_chkonk:20160706013810j:plain

というわけで、CounterをObservableにしてあげましょう。

値が書き換わったら、notifyメソッドを読んで、自分を監視してるひとたちに状態を通知してあげるわけです。

f:id:nkgt_chkonk:20160706013811j:plain

さて、それに対して、VC側ではこのCounterをobserveして、状態変化を検知して、その結果をUILabelに反映するようにします。

f:id:nkgt_chkonk:20160706013812j:plain

いいですね。これでひとまず完成です。

f:id:nkgt_chkonk:20160706013813j:plain

これ、Observerパターンを利用してModelの状態をViewControllerに開示したことが結構ポイントです。CounterモデルがViewControllerやUIViewを保持して、Counterの値が書き換わったときにそれらのメソッドを読んでViewControllerやViewに変更を伝える形にしてしまうと、CounterがViewModelやViewに依存してしまします。つまり、CounterをテストしようとしたときにViewControllerやViewが絡んできて、めっちゃテストしにくくなりますよね。

でもObserverパターンを使えば、「Modelの値や変更をPresentationに開示はするが、Presentationに依存しない」という状態を保つことができます。実際さっきのCounterクラスってimport Foundationしてるだけでしたよね。テスタブルです。

あとですね、モデル層がCocoaTouchフレームワークに依存しなくなったことで、モデル層は熟練のiOSアプリプログラマ以外にも読めるし書けるような内容になってますよね。これっていいことですよね。レビュー可能な人間が増えるし、分業もしやすいし。実際さっきの連打アプリのモデル層、iOSアプリ書けない人でも内容は理解できたと思います。

f:id:nkgt_chkonk:20160706013814j:plain

実際ファウラー先生も、冒頭で紹介した記事でこんなこと書いてて、「あー、PDSが実現できて最高だね、よかったよかった」って感じですね。

f:id:nkgt_chkonk:20160706013815j:plain

というわけで感動のエンドロールにいきましょう。このspecial thanksって書かれてるのはプレゼンの練習に付き合ってくれた友人たちです。あ、あと聴いてくださってるみなさんにもspecial thanksを。いやー、よかった、とか思ってたらですね。

f:id:nkgt_chkonk:20160706013816j:plain

ディレクターから割り込みが入るわけですよ。エンドロール止めましょう。最悪です。

f:id:nkgt_chkonk:20160706013817j:plain

なにかっていうと、今の実装だと、サーバーから数字が送られてきたときにカウンタの数字が4から10000とかにいきなり飛んだりするわけですよ。まあそれは「間違い」とか「バグ」とはいえないんだけど、最悪のUXだし、まあユーザはびっくりして「これバグじゃん」って思っちゃいますよね。たしかによくない、でもディレクタさん、あなたはプロなんだから「バグ」じゃなくて「よくないUI」くらいに言ってくれ、たのむ、という感じですね。

f:id:nkgt_chkonk:20160706013818j:plain

まあそんな感じで、現実はあまくないですね、エンドロールなんて見てないでまだ仕事しないとダメそうです。お疲れ様でした。

f:id:nkgt_chkonk:20160706013819j:plain

さて、とはいえ、やっぱりいいアプリ作りたいんで、UIを改善しましょう。これってUIの話なんで、プレゼンテーション層でやっていきますよ。

f:id:nkgt_chkonk:20160706013820j:plain

まず、UILabelをサブクラッシングしたカスタムなCounterLabelってのを定義します。これは内部に「ほんとのカウント」と「今表示してるカウント」を持ってて、ほんとのカウントがセットされたらタイマー起動して表示してるカウントのほうをタイマーで1ずつupdateしていきます。で、表示してるカウントがほんとのカウントにおいついたらタイマー止めます。

これ、ロジックですよね。「どう見せるか」っていう関心なんで、Presentationの関心ですよね。いわゆる「プレゼンテーションロジック」です。これはViewに閉じ込めてしまいましょう。

f:id:nkgt_chkonk:20160706013821j:plain

で、VCで直接UILabelに値をセットしてた部分も、この変更に合わせて変更しておきましょう。これで、サーバーから値が送られてきたときにはいきなり数字が飛ぶんじゃなくて、見た目上はすごい勢いで数字が増えてるみたいに見えるようになったでしょう。めでたしめでたしですね。

ちょっと話がずれますが、今回はViewにプレゼンテーションロジックを書きました。MVPならPresenterにこういうロジック書けば、そこがテスタブルになっていいですね。

f:id:nkgt_chkonk:20160706013822j:plain

さて、MVPにするのがさっきみたいな古典的MVCにするのかはともかく、今回、UI上の問題を解決するときにはPに手を入れただけで、アプリケーションの本質的な問題をモデリングしたDには一切手が入らなかったことに注目してください。こんな感じで、PDSを守っているといろいろいいことがありますね。

とはいえ、実はこのアプリもまだまだぜんぜん完璧ではありません。

f:id:nkgt_chkonk:20160706013823j:plain

たとえば、今回ってサーバーから送られてきた数字をカウンタにセットする君をModelに書いちゃいましたけど、これって本当にそれでよかったんですかね……?

タイマーイベントがアプリケーションへの入力だと見なすのが自然なように、サーバーからの入力もアプリケーションへの入力であると見なすのが自然なのではないでしょうか。そうするとこれはプレゼンテーション層で管理すべき関心の気がします。

f:id:nkgt_chkonk:20160706013824j:plain

それを考えると、プレゼンテーション層にもう一個、ActionProviderみたいな層があってもいいかもしれませんね。ここには、タイマーイベント発行してくれる君だとか、サーバーからpushしてくる値を受け取ってイベントを発行してくれる君みたいなやつを定義しておいて、VCがこいつらを利用してモデルに処理をdispatchしたほうがいいかもしれません。

f:id:nkgt_chkonk:20160706013825j:plain

あと、今回Observerパターンを結構重要な要素として使っていますが、実はObserverパターンってmutableな世界が前提のパターンなんですよね。それは当たり前で、変化しないものは監視する必要がないからです。しかし、よく言われるように状態のmutationは問題を複雑にします。最近はだから「なるべくimmtableに寄せていこうぜ」というのがトレンドになってきてますよね。それを考えるとObserverパターンを素朴に使ってるのはまだまだ改善の余地がありそうに見えます。

あと、今回API呼び出しダミー実装でDomainModelに置いちゃいましたが、API呼び出しって本来Infrastructureがもつべき関心ですよね。DBとのやりとりと同じく、外部システムとのやりとりっていう技術的な詳細ですし。そう考えるとやっぱりAPI呼び出しの部分はInfrastrucreに持つべきなんじゃないかな、とかいろいろ考えるわけです。

f:id:nkgt_chkonk:20160706013826j:plain

あと、これは今回のGUIアプリの例とはあまり関係ないんですけど、Repositoryパターンってクエリと相性最悪なんですよね。「いろんなテーブルをjoinした上で特定のカラムしか必要ない」みたいなやつって、DomainModelで定義したオブジェクトに綺麗にマッピングできないことが多いです。あと、クエリの種類によってRepositoryにファインダーメソッド定義しまくっていくと、異常な量のメソッドがRepositoryに生えてきて破滅します。

テーブルと1:1で設計できたARのときはまあよかったんですけど、Repositoryパターンを使ったことで、ここにきていわゆるOOとRDBの間のインピーダンスミスマッチ問題に悩まされるようになったわけです。つらいですね。

f:id:nkgt_chkonk:20160706013827j:plain

で、こういうことを考え始めると、いろんなことが気になってきます。さっきのActionProviderとか、immutableな設計とか考えると「おっこれってFluxアーキテクチャに近づいて行っていないか?いっそFluxにするべきか?」とか、「リポジトリパターンでうまくクエリが扱えないなら、コマンドとクエリを別にモデリングしちゃうか、あ、それってCQRSだよな」とか「じゃあいっそEventSourcingまで持って行ってしまうか?」とか、まあいろいろと本当に考えることが増えてくるんですね。これは完全に設計沼です。

そういうときには、「わたしたちはどんなビジネス要請によってアプリケーションを開発していて、このアプリケーションは何を解決するためのものなのか」ということに立ち返ってみましょう。単なるCRUDアプリケーションに対してCQRSを適用するなんて馬鹿げた話です。今からとても大事なことを言いますが、世の中には「最高の設計」なんてなくて、問題や課題に対する「最適な設計」があるだけなんです。だから、最初の話に戻りますが、「万能なモデルの正しい設計」なんてないんです。

f:id:nkgt_chkonk:20160706013828j:plain

そんなわけで、ここから先は、みなさんの作るアプリケーションがどのような課題に対するものなのかによって物語が変わってきます。「ここからさきは君自身の目で確かめる」しかないわけです。つらい。つらいですが、ここで唐突にゼルダの伝説に出てくるトライフォースの話をしましょう。

トライフォースはみっつに分かれているのですが、そのみっつとは、力のトライフォース、知恵のトライフォース、勇気のトライフォースです。

様々なパターンを知り、そのパターンのメリット、デメリット、そして何を解決するパターンなのかを知れば、それはあなたの力になります。力のトライフォースですね。

ただ、力があるだけではダメで、今度は課題に対してどのようにその力を使っていくのかを知恵を絞って考える必要があります。これが知恵のトライフォースです。

そして、正直いって、複雑な現実に立ち向かうのはかなり厳しいです。勇気を持って、力を知恵を運用することで初めて、良い問題解決が生まれるのではないでしょうか。

f:id:nkgt_chkonk:20160706013829j:plain

まとめます。

  • PDSを心がけることでいいことがたくさんあります
  • MVWはPについては設計指針をくれます
  • Mについては設計指針をくれません
  • Mについてはあなたがどのような課題に向き合っているのかによって適切な設計が変わってきます。
  • 大変な旅になりますが、トライフォースがあなたの力になるでしょう。

f:id:nkgt_chkonk:20160706013830j:plain

最後に宣伝させてください。わたしが働いている株式会社リラクでは、普段からこういう話をプログラマ同士でやりながら開発を進めています。みなさんの会社もそうかもしれませんが、正直いって無限にやるべき仕事あるので、仲間が欲しいです。一緒に、課題に対して最も適切な設計を追求していきたい仲間を募集しますので、「あっなんだか雇用が流動化しそうになってきた」みたいな方は是非声をかけてください。一緒に悩みながらやっていきましょう。

f:id:nkgt_chkonk:20160706013831j:plain

宣伝で最後終わるのちょっといやなんで、またまとめのスライド写して、トークを終えさせていただきます。ご清聴ありがとうございました。

Q & A

  • Q「ActiveRecordパターン使ってるとViewで複数テーブルから必要な値だけ引いてきてみたいなときにつらいことがあるんだけど相性悪いの?」
  • A 「そのクエリとARパターンの相性がわるいと言っていいと思います。わたしはそういうときは、クエリに関してはSQL直接書くレイヤーである「ReadLayer」っての作っちゃって、そこでSQL組み立ててそのViewに特化したDTOにレコードぶっ込んでPresentation層に渡しちゃったりしてますね。

結び

以上です。どうだったでしょうか。最後にちょろっと出てきましたが、弊社ではプログラマを募集しています。興味のある方は是非ブコメなどに「興味あるな〜。@your_twitter_screenname まで連絡くれないかな〜」とか書いておいてください。あるいは @neko_gata_s にリプライをくれてもいいです。一緒に悩みながらやっていく仲間がほしくてたまらないんです!よろしくおねがいします。

YAP(achimon)C::Asia Hachioji 2016 mid in Shinagawa

こんにちは。リラクヘルステックチームの泉原です。
エンジニアではないのですが、リラクでの店舗経験を元に、未来のヘルステックメンバーのアシストをさせて頂いております。
リラクゼーション業が、ITテクノロジーとの融合で新しい文化を創る場に立ち会えて、とても嬉しく感じています。
これからちょこちょこブログも更新するかと思いますので、よろしくお願いします!

今回は、イベントのご案内です。
題名の通り、技術者による、技術者向けのイベント・YAPCに、リラクヘルステックチームの丸山・近藤が登壇させて頂くことになりました!

YAPCについて

YAPCとは、Yet Another Perl Conference の略称です。
カンファレンスというと、高い参加費が必要な場合や、マーケティング寄りのものが多くなりがちですが、YAPCは高い参加費を払うことなく、技術者向けのトークを技術者から聞ける、Perl関連の人なら誰でも気軽に参加できるカンファレンスとしてはじまりました。
エンジニアのお祭りという位置付けでもあり、世界各国で開催されています。

過去の開催

日本でのYAPCは、 YAPC::Asia Tokyo が過去10年間開催をしていました。
大変人気のあったカンファレンスでしたが、開催10周年を目処に一区切りとし、昨年の開催が最後、ということで公表されています。

今回のYAPC

今回のYAPC主催者様は、YAPC::Asia Tokyoに、聴講/登壇/運営において全て参加経験のある方が、YAPCへの敬意を持って、ボランティアで企画をしてくれたイベントです。
YAPC::Asia Hachioji 2016 mid in Shinagawa

イベントは、7/2(土)・7/3(日)の2日間、品川のマイクロソフトジャパン社のセミナールームで開催されます。
イベント開始まであと3週間もありますが、既に一般参加(抽選)の応募数が来場最大数を超える日程もあるほど、期待値の高いイベントです。

そんなイベントに、リラクからは丸山と近藤が、登壇者として参加させて頂きます。
トーク内容は以下の通りです。

トーク内容

あの日見たM-V-WhateverのModelを僕たちはまだ知らない

トーク概要

MVCやMVP、MVVMなど、様々なアーキテクチャがとにかくいろんなところで語られ尽くされ、「もうM-V-Whateverの話はいいよ」となっているように思われる昨今ですが、M-V-Whateverをめぐる議論は混乱のさなかにあるまま「トレンド」から外れ、忘れ去られて行っているように感じます。このトークでは、Presentation Domain Separationの基本に立ち返った上でM-V-Whateverを再度確認し、その結果として当然でてくる次の疑問「ではModel自体のアーキテクチャはどう考えたらいいの?」に切り込んで行こうと思います。

概念的なトークだけではなく、私が今まで実際に作ってきたプロダクトの経験をもとに具体的な話を交えてM-V-WhateverとModelについてお話しできればと思います。

想定聴講者

「M-V-Whateverの話ってなんかいろんなひとがいろんなこと言ってるしRuby on RailsのActiveRecordとM-V-Whateverの話がうまく繋がってこないしそのあたりの議論がなんかぐちゃぐちゃしててわけわかんないんだけど‥‥」という感じのひと

登壇者

しんぺい a.k.a. 猫型蓄音機(丸山)
GitHubアカウント

登壇日

7/3(日)

ScalaでつくるInvalid stateのない世界

トーク概要

アプリケーションを作るとき、避けては通れないバリデーション。

バリデーションに関しては一家言ある方も多いと思いますが、私が実際にScalaで開発している中で得た「こういう感じでバリデーションというものを捉えれば良さそう」という知見や、「で、実際にこんな風にしてみました」というのを紹介します。

想定聴講者

非破壊オブジェクトを善しとする環境(Scalaなど)でのバリデーションに興味がある方

登壇者

takkkun(近藤)
Twitterアカウント

登壇日

7/2(土)

最後に

まだまだ一般参加の応募も募っております。
ぜひお越し頂き、一緒にYAPCを盛り上げましょう!