Patterns of Enterprise Application Architecture読書メモ(データソース編)

Martin FowlerのPatterns of Enterprise Application ArchitectureすなわちPoEAA、何年か前に原著をKindleで買って読もうとして序盤で挫折していた。最近またデータソースアーキテクチャについて翻訳版と原著を合わせて読んでいるので、整理のためメモを残しておく。

パターン

本書で紹介されるパターンはアプリケーションアーキテクチャから通貨の値オブジェクトまで、粒度もレイヤーも様々。今回はデータソースアーキテクチャパターンを理解するのに必要な箇所だけ調べた。

ドメインロジックパターン

  • トランザクションスクリプト
    • ほとんど構造化されてない書き下しなロジックのこと。ちょろいスクリプトならこれでもいいけど、ちょっと複雑になるとすぐに破綻する。
  • ドメインモデル
    • 業務上の概念をオブジェクト化して、それらを組み合わせて複雑なロジックを構成する。データマッパーサービスレイヤーパターンと組み合わせると良い。
  • テーブルモジュール
    • 上記二つの中間。RDBの1テーブルに対応するクラスでドメインロジックを構成するため、あまり複雑なアプリケーションには適合できないが、RDBなどとの相性は良い。
  • サービスレイヤ -

データソースアーキテクチャパターン

データベース操作のSQLとアプリケーションロジックの分離の仕方、ModelがControllerとデータベースの変換をどう扱うのかという話題。

  • テーブルデータゲートウェイ
  • 行データゲートウェイ
  • アクティブレコード
  • データマッパー

の4パターンが紹介されている。

テーブルデーゲートウェイ

P of EAA: Table Data Gateway

  • ひとつのデータベーステーブルがひとつのオブジェクトインスタンスになるゲートウェイ。テーブルへの操作(find,update,insert,delete)を備える。
  • Record Set(ジェネリックなデータ構造)やTable Module パターンとの併用に適している。
  • データベースの戻り値のRecord Setを返してもいいし、ドメインオブジェクトにマッピングして返してもいい。Record Setを返してデータ変換オブジェクトを用いれば、ユーザーが好きな方を選べて良いのではないか
  • ドメインモデルを使っている場合、テーブルデータゲートウェイドメインオブジェクトを返せるが、双方向の依存性が発生するのであんまり良くない
  • テーブルモジュール

// この例ではFindメソッドの戻り値にジェネリックなRecordSetを返却しているが、上の議論のように別にPersonオブジェクトを返しても問題はない。
class PersonGateway {
  Find(id): RecordSet
  FindWithLastNames(String): RecordSet
  Update(id, lastname,firstname, numberOfDependents)
  Insert(lastname, firstname, numberObDependents)
  Delete(id)
}
...
RecordSet r = personGateway.Find(1)
printf( r.Column("lastName") )
personGateway.Insert(2,"John","Doe",3)
personGateway.Update(2,"John","Doe",4)
personGateway.Delete(2)

行データゲートウェイ

P of EAA: Row Data Gateway

  • クエリ結果の行ひとつひとつがオブジェクトのインスタンスになる。ドメインオブジェクトがp.update()とか振る舞うのでオブジェクト指向感がある。
  • でもインメモリのオブジェクトにデータベースのアクセスコードが仕込まれる必要があるので、データベースとオブジェクトが密に結合してテストがやりにくかったりするデメリットがある。
  • トランザクションスクリプトに最適。
  • Findの動作をどこに定義するのかが問題になりがち
  • 行データゲートウェイに含まれるのはデータベースアクセスロジックだけ。ドメインロジックが含まれたらアクティブレコードパターンに進化する。

//static Findメソッドは別クラス(e.g. PersonFinder)に実装してもよい

class PersonGateway{
  lastname
  firstname
  numberOfDependents

  static Find(id): PersonGateway
  static FindWithLastName(String): PersonGateway[]

  insert()
  update()
  delete()
}
...
p1 = new PersonGateway()
p1.firstname = "John"
p1.lastname = "Doe"
p1.insert()

p = PersonGateway.Find(2)
p.numberOfDependents = 10
p.update()
p.delete()

アクティブレコード

P of EAA: Active Record

  • 行データゲートウェイのクラスにドメインロジックを追加したようなもの。
  • 行データゲートウェイテーブルデータゲートウェイドメインモデルとあまり相性が良くない。ドメインモデルであればアクティブレコードが向いてる(複雑なものでなければ)。
  • アクティブレコードクラスは以下のメソッドを持つ
    • SQLの結果行からアクティブレコードインスタンスを構築
    • 新しいインスタンスを構築
    • 静的なfindメソッドを使用してアクティブレコードオブジェクトを返却
    • データベースにアクティブレコードデータを挿入
    • フィールド取得、設定
    • ビジネスロジックの一部(バリデーションとか)
  • フィールドの型はテーブル内の各列と一致しなければならない。get/set時に他の効率のよい型への変換が認められる。
  • findメソッドは多くの場合静的に実装されるが、別に独立したクラスに分離してもよい。(テストもそのほうがしやすい)

class PersonGateway{
  lastname
  firstname
  numberOfDependents

  static find(id): PersonGateway
  static findWithLastName(String): PersonGateway[]

  insert()
  update()
  delete()

  getExemption()
  isFlaggedForAudit()
  getTaxableEarnings()  
}

データマッパー

P of EAA: Data Mapper

オブジェクトとデータベーステーブルは所詮別物(継承、コレクション、Joinの有無など)なので、1:1の対応でハンドリングするには限界がある。両者を変換するマッパがあれば複雑なドメインロジックと複雑なデータベーステーブルをハンドル出来るよ、的なパターン。

  • データマッパードメインオブジェクトとデータベーステーブルの間のデータの受け渡し(ロード/ストア)を責務とするソフトウェアレイヤ。ドメインモデルとデータベースを完全に分離し独立を保つ。ドメインオブジェクトはデータベースについて一切関知しなくてよくなる。
  • 全てのDB通信とマッピングデータマッパーが担うため、DBスキーマドメインオブジェクトそれぞれ独立して設計・開発が進められるメリット。
  • データマッパーがゲートウェイをラップすることはあり得る
  • 本書で紹介される「オブジェクトリレーショナルマッピングパターン」は、全てデータマッパーでオブジェクトとテーブルを接合させるためのストラテジ

class PersonMapper{
  insert(Person)
  update(Person)
  delete(Person)
}
class Person{
  lastName
  firstName
  numberOfDependents

  getExemption()
  isFlaggedForAudit()
  getTaxableEarnings()
}

データソースレイヤーアーキテクチャパターンの選び方

この本によると

  • ドメインモデルを使わない場合→ゲートウェイ系パターンでもなんとかなる
  • シンプルなドメインモデルを使う場合→アクティブレコードがよい
  • 複雑なドメインロジックを使う場合→データマッパーじゃないときつい

関連する基本パターン ゲートウェイマッパーの違い

ゲートウェイとマッパーはともに、ドメインオブジェクトと外部サービス(データベースなど)のインターフェイスとなる似たパターンだが、依存の方向性が逆方向になる。

ゲートウェイ

  • 外部サービスをラッピングして他のドメインオブジェクトが簡単に使えるようにする。
  • テストのためのサービススタブや、将来的に他のサービスに切り替えるためのポイントにもなる。間接化によって柔軟性を提供する。
  • PofEAA's Wiki - Gateway

マッパー

  • サブシステム間の通信を取り持つ分離レイヤー。サブシステム間の相互作用をハンドリングする。
  • PofEAA's Wiki - Mapper
    • 図のように、 マッパーが全ての関連サブシステムに依存している。サブシステムからマッパーへの依存はない。

その他のパターン

(今回は調べてない)

  • オブジェクトリレーショナル振る舞いパターン
  • オブジェクトリレーショナル構造パターン
  • オブジェクトリレーショナルメタデータマッピングパターン
  • Webプレゼンテーションパターン
  • 分散パターン
  • オフライン平行性パターン
  • セッションステートパターン
  • 基本パターン

エンタープライズ アプリケーションアーキテクチャパターン (Object Oriented Selection)

エンタープライズ アプリケーションアーキテクチャパターン (Object Oriented Selection)

Patterns of Enterprise Application Architecture

Patterns of Enterprise Application Architecture

Golangでの文字列・数値変換

覚えられなくて使うたびにググってしまうので、以後楽をするためにスニペットを記す。

パッケージ

strconvパッケージを使う。

文字列 → 数値変換(パース)

func Atoi(s string) (i int, err error)

文字列を10進数のint型にパースする。ParseInt(s, 10, 0)の省略形。

  var i int
  i, _ = strconv.Atoi("255")
  fmt.Println(i)  // => 255
func ParseBool(str string) (value bool, err error)

文字列をbool型にパースする。

  var b bool
  b, _ = strconv.ParseBool("true")
  fmt.Println(b) // => true

受け付ける値は1, t, T, TRUE, true, True, 0, f, F, FALSE, false, False

func ParseInt(s string, base int, bitSize int) (i int64, err error)

文字列を任意の基数(2進数〜36進数)・任意のビット長(8〜64bit)のIntにパースする。

  var i32, i64, ib16, ib0 int64
  i32, _ = strconv.ParseInt("255", 10, 32)
  i64, _ = strconv.ParseInt("255", 10, 64)
  ib16, _ = strconv.ParseInt("ff", 16, 16)
  ib0, _ = strconv.ParseInt("0xff", 0, 16)
  fmt.Println(i32, i64, ib16, ib0)   // => 255 255 255 255

baseは変換に用いる基数(2〜36)。0の場合、s文字列の書式から判断する(0x接頭詞がついていたら16進数など)。bitSizeは0,8,16,32,64(それぞれint, int8, int16, int32, and int64に該当)。どのbitSizeでも戻り値の型自体はint64だが、それぞれの型に値を変えずに変換できる。

func ParseUint(s string, base int, bitSize int) (n uint64, err error)

文字列を任意の基数(2進数〜36進数)・任意のビット長(8〜64bit)のUintにパースする。ParseIntと同様。

  var ui uint64
  ui, _ = strconv.ParseUint("255", 10, 32)
  fmt.Println(ui) // => 255
func ParseFloat(s string, bitSize int) (f float64, err error)

文字列を任意のビット長(32,64bit)のUintにパースする。

bitSizeは32か64(float32とfloat64に相当)。戻り値の型自体はfloat64だが、それぞれの型に値を変えずに変換できる

  var f32, f64 float64
  f32, _ = strconv.ParseFloat("3.14159265359", 32)
  f64, _ = strconv.ParseFloat("3.14159265359", 64)
  fmt.Println(f32, f64) // => 3.1415927410125732 3.14159265359
ParseXXX関数のエラーハンドリング

ParseXXX関数のerrorの具象型はNumErrorで、(NumError).Errで範囲エラーか書式エラーかを判別できる。

  _, e = strconv.ParseInt("Bad number", 10, 32)
  if e != nil {
    if enum, ok := e.(*strconv.NumError); ok {
      switch enum.Err {
      case strconv.ErrRange:
        log.Fatal("Bad Range Error")
      case strconv.ErrSyntax:
        log.Fatal("Syntax Error")
      }
    }
  }

数値 → 文字列(フォーマット)

func Itoa(i int) string

func FormatBool(b bool) string

func FormatInt(i int64, base int) string

func FormatUint(i uint64, base int) string

各数値型を文字列にフォーマットする。

  fmt.Println(strconv.Itoa(255)) // => 255
  fmt.Println(strconv.FormatBool(true)) // => true
  fmt.Println(strconv.FormatInt(255, 10)) // => 255
func FormatFloat(f float64, fmt byte, prec, bitSize int) string

float64型を文字列にフォーマットする。

  fmt.Println(strconv.FormatFloat(1234.56789, 'b', 4, 64)) // => 5429687001335527p-42
  fmt.Println(strconv.FormatFloat(1234.56789, 'e', 4, 64)) // => 1.2346e+03
  fmt.Println(strconv.FormatFloat(1234.56789, 'E', 4, 64)) // => 1.2346E+03
  fmt.Println(strconv.FormatFloat(1234.56789, 'f', 4, 64)) // => 1234.5679
  fmt.Println(strconv.FormatFloat(1234.56789, 'g', 4, 64)) // => 1235
  fmt.Println(strconv.FormatFloat(1234.56789, 'G', 4, 64)) // => 1235
  fmt.Println(strconv.FormatFloat(1234.56789, 'G', 3, 64)) // => 1.23E+03

fmtは出力書式で'b','e','E','f','g','G'のいずれか。

precは桁数。eEFのとき小数点以下桁数。gGのとき全体の桁数。(g,Gはprecがfの少数点以上の桁数を満たしていればfまたはFになるらしい)

おまけ

たしかに。strconv.Atoiとfmt.Sprintだけ覚えておけば日常生活では支障無いですね。

デモ

Go Playground

Go言語のコマンドライン引数の使い方(サブコマンド等)

go言語でのコマンドライン引数の使い方でやや混乱したのでまとめる。

  • まとめること
    • flagの仕様
    • flagでgit push -fのようにオプション引数より前に非オプションの引数を置く方法・サブコマンドの例
    • go-flagパッケージのサブコマンドを試す

flagの仕様

Golang標準のflagパッケージは以下のように使う。

package main

import (
  "flag"
  "fmt"
  "os"
)

func main() {
  // -hオプション用文言
  flag.Usage = func() {
    fmt.Fprintf(os.Stderr, `
Usage of %s:
   %s [OPTIONS] ARGS...
Options\n`, os.Args[0],os.Args[0])
    flag.PrintDefaults()
  }

  var (
    opt1 = flag.String("opt1", "default-value", "First string option")
    opt2 = flag.String("opt2", "default-value", "Second string option")
  )
  flag.Parse()
  fmt.Println("opt1:", *opt1)
  fmt.Println("opt2:", *opt2)
  fmt.Println("args:", flag.Args())
}
$ ./argtest -h
Usage of ./argtest:
   ./argtest [OPTIONS] ARGS...
Options
  -opt1="default-value": First string option
  -opt2="default-value": Second string option

$ ./argtest -opt1 aaa -opt2 bbb AAA BBB 
opt1: aaa
opt2: bbb
args: [AAA BBB]

flag.Parse()関数は、"-"で始まらない(非フラグな)コマンドライン引数に到達すると、未解釈の引数をflag.Args()に格納し、パースをストップしてしまう。

なので、例:./argtest -opt1 aaa -opt2 bbb FILE1 FILE2...のようにオプション群を非オプション引数より前に置くのなら簡単にパースできる

flagでコマンドラインオプションをコマンドライン引数の後ろに置きたい

では、例:./argtest FILE1 -opt1 aaa -opt2 bbbという風にオプションを引数の後ろには書けないのだろうか。たとえばgit push -fみたいなサブコマンドをやりたいときに必要になる。

そういうときは、FlagSet型を使う。

これは複数のFlag設定を切り替えられるようにする仕組みだけど、FlagSetのParse()メソッドはパースする範囲をスライスとして渡せるので、これを利用して先頭に非フラグの引数を置いたり、サブコマンドを実現したりできる。

  fs := flag.NewFlagSet(os.Args[0], flag.ExitOnError)
  var (
    opt1 = fs.String("opt1", "default-value", "First string option")
    opt2 = fs.String("opt2", "default-value", "Second string option")
  ) 

  arg1 := os.Args[1]
  fs.Parse(os.Args[2:])
  fmt.Println("arg1:", arg1)
  fmt.Println("opt1:", *opt1)
  fmt.Println("opt2:", *opt2)
  fmt.Println("args:", fs.Args())
$ ./argtest XXX -opt1 AAA -opt2 BBB YYY ZZZ
arg1: XXX
opt1: AAA
opt2: BBB
args: [YYY ZZZ]

サブコマンドの実装についてはVegetaが参考になった。各サブコマンド内で別々のフラグを設定している。See vegeta.

一応、こんなかんじになる。

subcommand.go

go-flagsを試してみる

調査の一環で非標準パッケージのgo-flagsを試してみたので感想

  • フラグ・非フラグの順序関係なくすべてパースできる。便利だけど、逆に位置を固定したいときにどうすればいいのか分からなかった。
  • サブコマンドをサポートしているようなので試してみた(下記)けど、コマンドがgo-flagsに依存してしまうしちょっと大げさだった
  • 使わなくていいかも。
package main

import (
  "fmt"
  "github.com/jessevdk/go-flags"
  "log"
  "os"
)

type Options struct {
  Option1 string     `short:"1" long:"opt1" description:"First string option"`
  Option2 string     `short:"2" long:"opt2" description:"Second string option"`
  Put     PutCommand `description:"Command to put something" command:"put" subcommands-optional:"true"`
  Get     GetCommand `description:"Command to put something" command:"get" subcommands-optional:"true"`
}

var opts Options
var parser = flags.NewParser(&opts, flags.Default)

type PutCommand struct {
  PutOption string `short:"p" long:"put-opt" description:"First string option"`
}
type GetCommand struct {
  GetOption string `short:"g" long:"get-opt" description:"First string option"`
}

func (x *PutCommand) Execute(args []string) error {
  fmt.Printf("Putting someting (opt=%v): %#v\n", x.PutOption, args)
  return nil 
}
func (x *GetCommand) Execute(args []string) error {
  fmt.Printf("Getting someting (opt=%v): %#v\n", x.GetOption, args)
  return nil 
}

func main() {
  if _, err := parser.Parse(); err != nil {
    os.Exit(1)
 }
}
$ ./command --opt1=AAA put XXX YYY ZZZ --put-opt=OPT
Putting someting (opt=OPT): []string{"XXX", "YYY", "ZZZ"}

$ ./command --opt1=AAA get XXX YYY ZZZ --get-opt=OPT 
Getting someting (opt=OPT): []string{"XXX", "YYY", "ZZZ"}

$ ./command --opt1=AAA
Please specify one command of: get or put

./command VVV --opt1=AAA get XXX YYY ZZZ --get-opt=OPT
Getting someting (opt=OPT): []string{"VVV", "XXX", "YYY", "ZZZ"} // <- argsにgetより前の引数が含まれている

参考

tus - Resumable File Uploads

Resumable File Uploads

tusというHTTP/1.1上で再開可能なファイルアップロードの規格を策定するプロジェクトを見つけた。2013年4月に始まった取り組みで、リファレンス実装が多い。提案されているプロトコルでのアップロードの流れをメモする(v0.2.1準拠)

初期化(File Creation)

空のPOSTリクエストでファイルを作成する。Entity-Length ヘッダはファイル全体の長さを表す。

リクエスト
POST /files HTTP/1.1
Host: tus.example.org
Content-Length: 0
Entity-Length: 100
レスポンス

新規に作成されたリソースのURIがLocationに乗って201が返ってくる。

HTTP/1.1 201 Created
Location: http://tus.example.org/files/24e533e02ec3bc40c387f1a0e460e216

アップロード

HEADリクエストで、どこまでアップロードされているかを取得する(Offsetヘッダ)

リクエスト
HEAD /files/24e533e02ec3bc40c387f1a0e460e216 HTTP/1.1
Host: tus.example.org
レスポンス
HTTP/1.1 200 Ok
Offset: 70
リクエスト

オフセット70バイトからPATCHメソッドでアップロードする

PATCH /files/24e533e02ec3bc40c387f1a0e460e216 HTTP/1.1
Host: tus.example.org
Content-Type: application/offset+octet-stream
Content-Length: 30
Offset: 70

[remaining 30 bytes]
レスポンス
HTTP/1.1 200 Ok

その他

チェックサム、並列チャンク、メタデータ、ストリームについてこれから定義予定らしい。

ファイル作成がPOSTで示されているけど、PUTでリソース指定したい時とか、リソースを更新したい時はどうするんだろうなと思った。暇があったらもうちょっと調べる。

(翻訳) Trelloは何が違うのか by Joel Spolsky

最近、rebuild.fm で紹介されていたタスク管理ツールのTrelloを試しに使ってみたんですが、個人的にかなりヒットでした。このTrello、Joel on Softwareで有名なJoel Spolsky氏のサービスだと知って驚いたんですが、氏のTrelloに関するブログエントリーが興味深かったので、著者の許諾を得て翻訳します。


原文:How Trello is different - Joel on Software

Trelloは何が違うのか(How Trello is different)

2012年1月6日 金曜日

つい数ヶ月前、私たちは非常にシンプルなWebベースのチーム・コラボレーション・システムのTrelloをローンチした。まだ始まったばかりの1.0状態だというのに、フィードバックは圧倒的に好感触で、採用の勢いは非常に力強い。

Fog Creekにとって、Trelloは新しい種類の開発プロジェクトだ。100%ホストされており、「インストール・ソフトウェア」バージョンのTrelloというものは決して存在しないだろう。このおかげで、私たちは開発プロセスを多くの面で現代化できた。私たちはTrelloに関わる いかなる部分にもVisual Basicコードが全く含まれていない ことをお知らせ致します。次はなんだ?空飛ぶ車か?

あなたが(もっぱらソフトウェア開発者向けに投入された過去製品と比較して)気づくであろう最も大きな違いは、Trelloが完全に水平的なプロダクトだということだ。

水平的とは、様々な職業の人々がそれを使えるということだ。ワープロやWebブラウザーは水平的だ。歯医者がドリルであなたを拷問にかけるために使うソフトウェアは垂直的だ。

垂直的ソフトウェアは成功させやすくお金を儲けやすいので、あなたの最初のスタートアップとしては良い選択だ。これには2つのキーとなる理由がある。

  • 顧客を見つけやすい。もしあなたが歯医者ソフトを作るのなら、あなたはどの集会に出かけて、どの雑誌に広告を打てばいいのか知っている。ただ歯医者を見つけさえすればいいのだ。
  • 利ざやが良い。あなたのユーザーは仕事のプロフェッショナルで、あなたが彼らの問題を解決できるのなら、彼らがあなたにお金を払うのは理にかなっている。

様々な職業の人にとって便利な水平的な製品を作るというのは、成功させるのは殆ど不可能だ。あなたは、開発コストを膨大なユーザー数で償却できる他の水平的プロダクトと競合しているので、あまり高額な料金を請求することはできない。ハイリスク・ハイリターンだ。ゼロから起業したスタートアップには向かないが、Fog Creekのように成熟し安定した起業の2つ目や3つ目の製品のアイデアとしては悪くない。

すまないが、1991年に戻って私がMicrosoft Excelチームにいた時の話をさせてくれ(ああ、確かにきみが当時まだ生まれていなかったのは認めるが、コンピューターは発明されていたんだ。分かったら僕の膝に飛び乗っててじっとしていてくれ)。

誰しも、Excelを財務モデリングアプリケーションだと考えていた。これは式やそのようなものを使って、計算モデルを作り出すために使われていた。例えば、"もし金利が来年0.00001%増加するなら、ラスベガスの住宅所有者の何パーセントが破産するか?"のような計算をするものだと思っておいてほしい。

1993年前後、私たちのうち二、三人が、人々がどのようにExcelを使っているかを知るために顧客のもとに出かけた。

私たちはそこで、高度に統制された巨大な公共事業の"今週の負傷者数"スプレッドシートを管理することだけが仕事だという男を発見した。

彼は週に一度、施設名と、その週に負傷者が0であったという意味の数字の0を含む、十の施設が列挙されたExcelスプレッドシートを開いた(負傷者が出たことは一度もなかった)。

彼は今日の日付をスプレッドシートの一番上に入力し、印刷し、三穴バインダーに閉じ、そしてそれが彼の、まさに仕事のすべてだった。少し悲しいことだった。彼は一日二度の昼休みを取っていた。私だってもしそれが私の仕事の全てだったらそうしただろう。

次の二週間、私たちは数十のExcelの顧客の元を訪れたが、Excelを実際に「計算」と呼べるもののために使っている人間はどこにもいなかった。彼らのほとんど全員は、表を作るのに便利だからExcelを使っていたのだ。

(無関係な注:私たちが見つけた数少ない「計算」をしていた顧客は銀行で、彼らは「デリバティブ」と呼ばれる爆破装置を考えだしていた。彼らは10年のうち9年は銀行家のボーナスを最大化し、10年に一度西洋文明に崩壊を引き起こしかけるためにExcelを使っていた。ブラック・スワンに関する何かだ。多分、浮動小数点の丸め誤差だろう)

 何の話をしてたんだっけ?そうそう、ほとんどの人々がExcelをリストを作るために使ってたということだ。突如として私たちは、Excelを時代遅れにせんとした、上等な未来的スプレッドシートであるLotus Improvがなぜ完全に失敗したのかを完全に理解した。あれは計算が大得意だったが、表の作成はひどく下手で、そしてみんなはExcelを計算ではなく表のために使っていたからだ。

ビンゴ!頭のなかで明かりが灯った。

素晴らしい水平的キラーアプリケーションとは、実際には着飾ったデータ構造に過ぎないのだ。

スプレッドシートは単にwhat-if分析をするための道具ではない。これらは特定のデータ構造を提供する。表だ。ほとんどのExcelユーザーは式を入力しない。彼らは、表が必要な時にExcelを使う。再計算などではなく、罫線こそがExcelの最重要機能だったのだ。

ワープロは単に本やレポートや手紙を書く道具ではない。これらは特定のデータ構造を提供する。自動的に折り返されてページに分割されるテキストの列だ。

パワーポイントは単に退屈な会議を作るためのツールではない。これは特定のデータ構造を提供する。フルスクリーン画像の配列だ。

Trelloを見て「お、カンバンボードじゃないか。アジャイルにソフトウェアを開発するためのものだろ」という人もいる。ああ、そのとおりだとも。でも結婚式の計画にも使えるし、休暇に出かける場所の候補を家族にシェアするのにも使えるし、求人の応募の経緯を記録するのにも使えるし、他の無数の用途にも使える。実際のところ、Trelloはあなたが人々のグループとともにリストのリストを管理したいと思う全ての場所で使えるのだ。

そのような種類のデータ構造を必要とするものは無数にあり、そしてTrello以前に素晴らしい「リストのリスト」アプリケーションは存在していなかった(アウトラインプロセッサはあったが、あれは私に言わせればUIデザインの袋小路だ。だからプログラマーには魅力的だが、一般人には役に立たない)。

いちどTrelloに足を踏み入れれば、あなたは全てのことにこれを使うだろう。私は常に約30個のTrelloボードを使い、それらをいままでみんなと使ってきた。年老いた両親から、ともに休暇を計画する人と、仕事上のすべてのチームと、私が関わっているほとんど全てのプロジェクトで。

だから、オーケー、これがTrelloの最初の大きな違いだ:垂直的ではなく、水平的であること。しかし他にもたくさんの違いがある。

継続的にデリバリーされている。 メジャーリリースとマイナーリリースがあるというよりも、私たちはほとんど単に、新しい機能を開発者のもとから顧客に押し出すだけだ。あなたが開発してテストして、しかし次のメジャーリリースを待っているがために届けられていない機能というのは、在庫になる。在庫は重荷だ。あなたが費やしたお金は、売上を上げることもなくどんどん無駄になってゆく。たしかに、100年前には私たちも「CD-ROM」と呼ばれるものを持っていて、そういう方法でソフトウェアを出荷していた。だから私たちは世界中の顧客にそれを押し付けるまで機能を一箇所に集めておく経済的な理由があった。だが、もはやそういう方法で仕事をする理由は存在しない。もちろんもう知っているだろう。ちょっと言ってみるだけだがー5分前にVisual Basicを使うのを止めたんだ。「すばらしい新世界」だ。

リリース前に徹底的なテストをしていない。 私たちはそれでもなんとかなると考えた。なぜならTrelloは無料で、顧客はより寛容だからだ。だが本当のことをいうと、本当の理由はバグは数ヶ月ではなく数時間で修正されるので、「人々が経験するバグ」の数は少ないからだ。

取り組みを公開している。 Trelloチームのルールは「デフォルトで公開」だ。私たちが現在取り組んでいることとこれから取り組むものを全て一覧できる公開のTrelloボードがある。私たちはこれを、顧客にお気に入りの機能に投票とコメントをしてもらうために使っている。ところでTrelloの開発中には、これは秘密だった。開発チームがリーン・スタートアップの原則を使えるように、私たちはカスタマーフィードバックをくれるたくさんのベータテスターを抱えていたが、バージョン1.0をつくり上げるために費やしたその秘密の9ヶ月は、我々に競争市場で大幅なリードをもたらした。だた今や私たちは製品を出荷したので、私たちの計画を喋らない理由はない。

”ベンとジェリーの”ではなく、”高速に成長する”製品である。 Strategy Letter Iを読んでくれ。Trelloのビジネスゴールは、究極的には10億人のユーザーを獲得することだ。これは、私たちの最優先事項が、採用を妨げるいかなる障害も取り除くことであると意味している。人々がTrelloを使わない理由に挙げそうな全ての事柄を発見し、排除する必要がある。例えば:

Trelloは無料である。 製品に料金を請求することでおきる摩擦は、大規模な成長のための最大の障害だ。長い目で見れば、少数のユーザーから大量のお金を取り出す方法より、多数のユーザーから少量のお金を取り出す方法を見つける方がずっと簡単だと私は思う。ひとたび10億人のユーザーを獲得すれば、その中の誰があなたの作った製品から最大の価値を得ているか見つけるのは簡単だ。その最大価値を受けている人は、あなたに喜んでお金を払うだろう。他の者はそれほどサポートコストがかからない。

APIとプラグインアーキテクチャが最優先事項である。 他の言い方で言うとこうだ:もしそれが基本的なAPIを公開して高価値なユーザー(あなたのプラットフォームから最大価値を受け取っている人たち)が私たちのために作ってくれるものなら、決して自分たちで作るな。Trelloチームでは、プラグインで提供できる機能は全てプラグインで提供しなくてはならない。

APIは現在とても原始的な形をしている。既にそれを使ってなにか面白いことができるが、現在鋭意開発中だ。)

最先端の技術を使っている。 そのせいで私たちはたまに自分の指を切ってしまうのだが。私たちの開発者は、MongoDB, WebSockets, CoffeeScript, そしてNodeに血を流している。だが少なくとも、彼らは楽しんでいる。それに今日の厳しいジョブマーケットでは、偉大な開発者は彼らが何に取り組むかについて、大きな支配権を持っている。あなたが彼らに、何百万人もの人々を感動させるエキサイティングな製品を与え、彼らが単純な仕組みがうまくいっていない理由を探り当てようとしている間にTCP-IPの奥深くまで潜らせることができるなら、彼らは楽しみ、仕事を愛するだろう。それに、私たちは次の10年に取り組むプロダクトを作っているのだ。今日の単なる「最新鋭」の技術は、五年後には古びて軋みが生じるだろう。私たちは「最新式」をほんの少しだけ上回りたい。これは計算済みのリスクなのだ。

過激すぎるものはひとつもない。要するに、Fog Creek Softwareは全てのY Combinatorのスタートアップが、spezが真夜中にLispが壊れた時にRedditをリブートするためにラップトップを抱いて寝ていた頃から使ってきたテクニックでインターネット製品を作る。もしまだTrelloを試していないなら試してみてくれ。うまくいったら、私にTwitterで教えてくれ

Joel on Software

Joel on Software

More Joel on Software

More Joel on Software

#pyfes 2013.11に遊びに行ってきました。

pyfes/201311.rst at develop · pyspa/pyfes

Python Developers Festa 2013.11 - connpass

Pyfesに行ってきました。土日に昼まで寝ている自分としては9時45分の開場期限に間に合っただけで快挙でしたが、Go, HTTP2.0, ストレージ, 構成管理と、今個人的に興味のある分野の話が聞けて有益な一日でした。会場のオラクル青山センターはトイレがオシャレでした。以下、発表内容メモ(聞き漏らし聞き間違いあり)

Go ハンズオン @ymotongpoo

教材:Talks - The Go Programming Language

P2Pチャットシステムを実装するハンズオン。自分は参加資格であるA Tour of Goが半分までしか終わらなかったので、参加するでもなく話を聴いてました。A Tour〜終わらせてから改めて挑戦したいです。Goは書けるようになりたいですね。

LT

原稿募集のお知らせ(Sphinx可)@turky氏(オライリー・ジャパン)

原稿募集のお知らせ // Speaker Deck

  • オライリー・ジャパンがあなたの原稿を電子書籍にします。
  • 達人出版会だけじゃないんですよ!
  • 数十ページから200ページ位の分量でOK
  • テーマ選択や構成案から相談に乗ります
  • 今ならAuthorキャップが貰える!
  • ReVIEW でやってるけどSphinxでおk
  • 編集者がアドバイス/サポートします
  • 連絡ください

Pythonistaもlsを読むべきか? @flyingfoozy

Pythonista も ls を読むべきか?

PythonでのファイルシステムAPIの性能の話

PyConJP2014 開催に関するお知らせ @shkumagai

  • PyCon JP 2014スタッフ・メディアチーム
  • 座長が変わり、動き始めたところ
  • 小さな組織で動いていく
  • スタッフ募集中

HTTP2.0 概要 @jxck_

HTTP2 & HPACK #pyfes 2013-11-30

  • 2013年11月にdraft-08策定。今日はその話
  • HPACK(ヘッダ圧縮)が途中から別のRFCに分かた
  • HTTP/1.1はドキュメントベースだった。ステートレス。
    • pipelining/keep-aliveで延命してきたよね
  • HTTP2は今は割とSPDYとは違う感じになってる
  • 2014年春に決まる予定だが無理なのでもっと先になる
  • Google的にはどこかでSPDYを切ってHTTP2.0にしていきたい
  • HTTP2.0のRFCはGithubで管理されてる。議論はML
  • ネゴシエーション、ALPN/Update
  • HTTP/2.0始め方
    • http:// の場合 Upgrade Header
    • https:// の場合 ALPN (application layer protocol negotiation)
    • 対応が自明な場合は最初なからhttp2.0でしゃべってもいい
  • TLS-ALPN、クライアントが喋れるプロトコルのリストを提出し、サーバーが選ぶ。(Accept-*みたいな?)
    • 虚偽申告なクライアントを叩き落とすMagic Octetsを使う
  • サーバープッシュ、必要になるリソースをクライアントのキャッシュに突っ込んでおく
  • 以前送ったヘッダーとのdiffだけ送る。スタティックなテーブルが定義されておりインデックスで送る(ハフマン圧縮)
  • ヘッダ、63byte→16byteに
  • HTTP2.0実装
    • 日本は世界で1番か2番目に実装の多い国に
    • wireshark + TCP dumpで実装しよう
    • nghttp2
      • 世界標準のCインプリ
    • HTTP2Cat
      • Webツールで試せるようにしたもの
  • オライリー High Performance Browser Networking

HTTP2.0 クライアント nghttp2プロジェクト @tatsuhiro_t

http2client-pyfes2013 - Google ドライブ

  • draft-06/07/08対応
  • libnghttp2はフレーミングの実装
  • HTTPセマンティクスやネットワークIOはアプリケーションの仕事
  • src/nghttp -nv http://twitter.com
  • ダイレクト接続
    • クライアント接続ヘッダー(24バイトのマジックオクテット)を送信。
  • NPNはALPNの逆。標準化されない
  • HTTP2におけるリクエスト/レスポンスの1トランザクションはストリームと呼ばれる
    • HTTPヘッダー → HEADERSフレーム
    • HTTPボディ → DATAフレーム
    • フレームにはサイズ16383バイト制限
      • 複数のフレームに分割して送れる
  • HTTPリクエストの多重化(これがHTTP2.0の本領)
    • 2では1サーバーに対して原則1接続。
    • リクエスト順にレスポンスを返す必要がない
    • クライアントはリクエストの優先度を設定できる(無視可)
  • プロキシ
    • CONNECTも問題なく多重化
  • フローコントロールの考慮
  • Firefox(try-build)がお手軽なクライアント実装
  • node-http2, http2(ruby)

nginx + mruby @cubicdaiya

mruby_nginx_module at pyfes 2013.11

  • https://github.com/cubicdaiya/mruby_nginx_module
  • mruby_nginx_module
  • ngx_mrubyとの違い
    • (mod|ngx)_mrubyはApacheとNginxと同じように書くのが目的
  • mruby
    • 軽量Ruby、組み込み向け
    • Luaから見るとCとの連携が非常に楽
      • Luaのスタック操作と比べると直感的
      • Luaはtableしかなくてつらい。
  • ngx_luaと のちがい
  • ngx_lua
    • nginxの設定ファイルをLuaで書く
    • nonblockingアーキテクチャと親和性が高い
    • lua-jitと比べると強い
    • 色んな物をまとめたものがOpenResty
  • mruby_nginx_module
    • まだまだ発展途上。とりあえずノンブロッキングにしたい
    • 今のところ使える機能
      • コードキャッシュ
      • ハンドラフック
      • nginxの各種機能へのアクセス
  • 記述例
    • 各処理フェーズでデータを共有できる
  • (mod|ngx)_access_token
    • S3のクエリ文字列認証っぽ機能を提供
    • 特定のアクセストークンにも基づいた認証
    • リソースの有効期限設定
    • 認証方式
    • Text = Method + Uri + Expires + AccessKey
    • hmac_sha1 = hmac_sha1(text, secretkey)
    • *_access_tokenと比べて10行で書けた

pixiv を支えるインフラの技術 @harukasan

  • PixivではGoがプロダクションに投入されました→拍手
  • 拠点、オフィスとIDCF+新DCに画像クラスタ
  • オフィス拠点ではベニアサーバーをラックにマウントしてる
  • pixiv Image Cluster

    • 画像配信クラスタ
    • 2010年から運用開始
    • メインコンテンツであるイラストを超高速に処理するために最適化
    • 2,30台、二段キャッシュ
      • 表がnginx、裏がATS
    • ユーザーIDベースでサーバーを分散
      • img1xx.pixiv.net
      • 40-60回のDNSリクエストが発生して家庭用ルータがDNS解決できなくなっていた
    • キャッシュ戦略
      • メモリキャッシュ + ディスクキャッシュ
      • スイッチ間トラフィックを削減するため
      • メモリ価格の定価
      • ioDrive2とか高いけど1/5、1/6の性能で良ければ安いSSDでいい -新イメージストア
    • 作成日 + IDベースのシーケンシャルなURI
    • nginx_luaがkyototycoonにオブジェクトのステータスを聞く
  • pixivのログ解析基板

    • PHPアプリケーションのログ、MySQL/neoagentのスロークエリログ、Front ServerのAccess Log→Fluentd→File System/MongoDB/ElasticSearch(kibana使いたいので)
    • Error Viewr
    • Slow Query Viewer
    • Kibana 3
    • エラー時にログがバーストしてFluentdが死ぬ対策でログをJSONにしてからFluentd ?
    • Munin/Nagios/Monit/Cron
    • Cluster Admin
    • Capistrano/Subversion
    • /etc/以下の設定ファイルがそのままSubversionの管理下のディレクトリに。
    • / 設定反映はCapistranoを使用して全台にデプロイ
    • LVS管理画面
    • MySQLの遅延監視
    • 「尖ったことはせず、無理せず運用出来る状態にもっていく」

「git-flow は死んだ」@troter

git-flowは死んだ - Google ドライブ

  • git-flow = A successful Git branching model
  • git-flowはブランチの命名規則、マージ方法を決めて管理するブランチ戦略の名前
    • masterはタグ専用、developは統合用…
  • メインラインモデルより「安定trunkパターン」に近い
  • git-flowはgit-flowをサポートするgit用のコマンド群
  • git flow master develop finish ← master,developにマージする
  • でも…git-flow使ってる?
  • git-flowの辛いところ
    • 操作が抽象化されてる、gitが上達しない
    • 実装上の制限、2つのブランチにしか同時にマージできない
    • ちょっとおおげさ
  • みんなはブランチ管理したくない。コードに集中したい。
  • ブランチを増やさないGithub-flow的な運用が流行ってるらしい
  • Github-flow
    • masterは常にデプロイ可能、開発はmasterからブランチを切る
    • 開発が進んだらmasterにPR
  • git-flowが発表された当時、GithubはPRをサポートしていなかった。git-flowはPR出現以前の開発フロー
    • PRでもっと軽量に
  • git-flowは死んだの?
    • もともと特定のプロジェクト用の運用方法だった
    • 運用できるかはプロジェクト次第
  • 巨大リポジトリの復権

nginx+Lua @moriyoshi

nginx_luaモジュールの技術解説/モジュール紹介

  • rewrite_by_lua
    • 認証のかかったS3コンテンツを転送
  • access_by_lua
    • 日本時間の9時〜21時以外のアクセスを禁止する
  • header_filter_by_lua
    • X-Powered-Byを強制付加して、PHPで書かれているように演出したい時に便利

組み込み関数

  • nginx.location.capture
    • 内部的にHTTPリクエストしてその結果でハンドリングできる
  • ngx.shared
    • 共有メモリで簡単なキャッシュで
  • Lua文法解説

Ansible入門 @r_rudi

Ansible入門...?

  • ロゴがかっこ悪いのは誤解
  • Chef/Puppet/Salt/cfengine/jujuとかと同じ構成管理ツール、プロビジョニングツール
  • 例のvelocity 2010のLee Thompsonの図
  • Ansibleはオーケストレーションコンフィギュレーションをカバー
  • 本日の前提知識
    • Ansibleはタスクを実行する
    • Playbook == Taskのセット
    • 必ず上から順に実行される
    • % ansible-playbook hoge.yml
      • -i inventry file -u username -k ssh pass -C check mode -D diff表示
  • モジュール
    • unarchive
      • untarする
    • shell module
      • ローカルのfoo.shをリモートにコピーして実行
      • fabricとかは、pythonで書く必要が。
    • 起動モジュールたくさん
    • 全部のモジュールが冪等性があるわけではない。
  • deployのしかた
    • wait_for = 「8080ポートが立ち上がるまでまつ」みたいな指定ができる。10秒くらいスリープしとこうみたいな感じじゃない。
  • rolling update
    • serial: 1 ←1台ずつ実行できる。10にすれば10台ずつ。
  • Python API
    • Ansible + Flask
  • AnsibleWorks AWX
    • きれいな画面でリリースできたり、権限管理とかできる。
    • 10台まで無料。
  • demo
  • 何台くらい扱えるの?
    • 5000台のサーバーに使ってるユーザーがいる
  • Accelarated mode
  • Arista networks
    • スイッチ製品
    • SSH/Python ready。スイッチもAnsibleで制御可能。かっこいい。
  • DBを扱うモジュールも多い。
  • Notification
  • まとめ
    • 自動実行ツールとしても使えるよ
    • moduleたくさん
    • 簡単に作れる
    • python不要
    • 実行速度も十分速い
  • 最近Ansible本が出た
    • 100ページくらいの平易な英語の本
    • Ansible Configuration Management

業界団体の運営について @aishimau

  • OEM Device Sales for Consumer market
    • 「だれが」「なんの」「なにを」「どうする」組織か
    • PC業界の マーケットの 拡大を 目指す
    • スポット的な季節労働
  • 会社のゴール設定を理解することが大切
    • サポート切れるXPどうするのか、とか
  • 業界団体の問題は、人が少ないので人がいなくなると回らない
  • 何にしても大事なのはこれら。これができればなんの仕事でもできる
    • 話を聞く
    • 理解する、傾聴する
    • 整理する
    • 考える
    • 説明する
  • さらに何を言われてもブレない心が大事。
  • 硬軟取り混ぜる。

定期プレゼン

Python 3.4 @torufurukawa

  • 構文/文法の追加無いけどライブラリいろいろ追加
  • asyncio
    • シングルスレッドで並行処理する。未ドキュメント
  • enum
    • 列挙型。定数を安全に評価できる。name,valueもてる
  • ensurepip
    • pipがインストールされた状態にする
  • pathlib
    • パス/ディレクトリ/ファイルをオブジェクトっぽく操作する
    • p = Path(file).parent.resolve()
    • dirname / filename ←でjoinするという… (エー)
  • selectors
    • selectモジュールは低レベル
    • selectのファクトリっぽい
  • statistics
    • 簡単な統計計算
  • tracemalloc

gemのslideshowでMarkdownからスライドショーを作る

gemのslideshowを使ってMarkdownファイルをスライドhtmlに変換する

slideshowとは?

slideshowは、Markdownファイルを様々なJavaScriptスライドライブラリを使ったhtmlファイルをビルドするgem。 一つのMarkdownからいろいろなテンプレートのスライドを生成出来るので便利。

インストールとサンプル

$ gem install slideshow
$ slideshow help  #オプションを確認
$ slideshow help build #buildオプションを確認
$ slideshow new   #サンプル用のwelcom.txtファイルが出力される。中身はMarkdownテキスト。
$ slideshow build welcome.txt

スライド.htmlファイルが出力されるので、ブラウザで開いてデフォルトテンプレート(s6)でスライドがレンダリングされるのを確認する

f:id:ono_matope:20131018174904p:plain

他のテンプレートを使ってみる

$ slideshow install shower
$ slideshow build welcome.txt -t shower

showerテンプレートでレンダリングされる

f:id:ono_matope:20131018175406p:plain

revealテンプレートを使ってみる

ここを参考に

$ slideshow update
$ slideshow install plugins
$ slideshow install reveal
$ slideshow build welcome.txt -t reveal

f:id:ono_matope:20131018175603p:plain

以上。remark.jsテンプレートがほしい。