ikasama over technology

忘れたくないことを忘れないために

レガシーをぶっつぶせ。現場でDDD! 参加レポート #genbadeDDD

genbade-ddd.connpass.com

申込み時点で +100 人くらいで補欠だったので諦めてたんですが、 当日の 0:30 に繰り上がり通知が来て、慌てて参加しました。

全体を通して、いくつか印象に残ったことを書いておきます。

  • 始める前にメンバーとの共通認識をつくる
    • DDD の方法そのものの学習
    • 業務知識の理解
      • エンジニア以外のメンバーとの共通認識も重要
    • 良い設計のためにはビジネスの理解が必要
  • まったく新しく取り組む場合はリスクを小さくする
    • 小さく始める
    • 重要だが緊急性が低いもの
      • 緊急性が高いと品質を犠牲に完成を強いる圧力がかかりがち
  • ビジネスサイドへの説明は簡単ではないが大事
    • 小さく始めると少ないコストで実績を積んで成果を確認できる
    • 刷新前と刷新後のコードベースに対して同じ改修をして効果測定をした

  • 変化に弱い = レガシーになりやすい
    • とりあえずマイクロサービスにすればいいというわけではない
    • サービス分割がイケてないとやっぱりポシャる

感想

  • 新規開発ではつまづかないような、レガシーと戦う上での知見が聞けてよかった
  • チームで DDD に取り組むための心構えができた
  • まだ仕事で実践できていないけど、学んだ内容の再確認ができた
  • DDD は銀の弾丸ではない。レガシーとの戦いは泥臭い作業の繰り返しだ。

zsh + zplug で最強でポータブルなターミナル環境を作りたい

タイトルで言いたいことは全部言いました。

なんとなくインストールしていた zplug, よく見ると何でもかんでもインストールできることに気づきました。

github.com

zplug "jhawthorn/fzy", \
    as:command, \
    rename-to:fzy, \
    hook-build:"make && sudo make install"

これを使えば、言語環境から各種 CLI ツールまで全部 zpug で管理できるのでは? 最終的に、まっさらな環境で

sudo yum install -y git zsh
git cllone git@github.com:ikasam/dotfiles.git
chsh -s /bin/zsh
exec zsh -l

と実行するだけで開発環境が出来上がるかもしれない。

導入するもの

次に新しい職場に行くまでに作っておきたい。

技術書典 6 の本の感想 / フリーランスを完全に理解できる本 / バーチャル幼女プログラマー きりみんちゃん 公式ファンブック

フリーランスを完全に理解できる本

kirimin-chan.booth.pm

  • フリーランスという働き方にまつわる事柄を 1 冊で知れます
    • 一つ一つのトピックは自力で調べられる内容だけど、コンパクトにまとまっていて良い
  • 単価水準は参考にしてみようと思いました
    • あくまで 2019 年現在、東京都内の Web 系エンジニアの相場です
  • 税金周りはサラリーマンでも知っておいて損はない内容だと思います
  • キャリアについては軽く触れられている程度ですが、すぐに実践しやすい内容です
    • このあたりを掘り下げたかったら、より詳しい専門書をあたると良いでしょう

SOFT SKILLS ソフトウェア開発者の人生マニュアル

SOFT SKILLS ソフトウェア開発者の人生マニュアル

バーチャル幼女プログラマー きりみんちゃん 公式ファンブック

kirimin-chan.booth.pm

  • 文字が少ないので脳のリソースを使わなくて良いです
  • 1 ページ目に Hallo kirimin-chan って書いてあるけど幼女キャラの演出かなと好意的に捉えています
  • 君も公式ファンブックを買ってきりみんちゃんを応援しよう!

CakePHP 2.x の PaginatorHelper をハックする

TL;DR

この CookBook にあるようなパラメータを $this->request->params['paging'] に渡すことで任意のページネーション表示ができます。

https://book.cakephp.org/2.0/ja/core-libraries/helpers/paginator.html#PaginatorHelper::params

実際にパラメータを渡すサンプル。

$params = [
    'page'      => 2,
    'current'   => 10,
    'count'     => 777,
    'prevPage'  => false,
    'nextPage'  => true,
    'pageCount' => 4,
    'order'     => null,
    'limit'     => 10,
    'options'   => [],
    'paramType' => 'querystring',
];

$this->request->params['paging']['YourModel'] = $params;

背景

いまどき CakePHP 2 かよ! とお思いかもしれませんが、なかなかレガシーから抜け出せない組織、ありますよね。 弊社ではようやく新しいフレームワークに移行することが決まったんですが、それがまさかの CakePHP 3 。 どうせならもっとほかのフレームワークにしてくれや……と内心思いながら移行の仕事をしています。

今回の移行は、利用者や役割の異なる複数のアプリケーションにそれぞれ書かれている、同じようなビジネスロジックAPI に一本化してしまおうという目的があります。 単なるデータフェッチなら API にまるっと移してほぼ同じ構造のデータを返してもらい、 Controller とかがそれを受け取ればいいだけです。 しかし Paginator は View に がっつり Helper として入っていて、正直修正したくない。 *1 ということで Paginator によるデータフェッチをレガシーから切り離しつつ、 レガシーの Paginator には出来合いのパラメータを渡して PaginatorHelper を生きながらえさせる作戦を考えました。

実装

PaginatorHelper のパラメータサンプルの URL を再掲します。

https://book.cakephp.org/2.0/ja/core-libraries/helpers/paginator.html#PaginatorHelper::params

各パラメータの意味はこんな感じ。間違っていたら指摘ください。

$params = [
    'page'      => 2,             // 現在のページ番号
    'current'   => 17,            // 現在のページに表示している件数
    'count'     => 3,             // 全体の件数
    'prevPage'  => false,         // 前のページがあるか?
    'nextPage'  => true,          // 次のページがあるか?
    'pageCount' => 4,             // 全体のページ数
    'order'     => null,          // ソート順
    'limit'     => 26,            // 1 ページの件数( {:start}, {:end} の計算に使うのはこちら )
    'options'   => [],            // ページネーションのオプション。なんかいろいろあるっぽい。
    'paramType' => 'querystring', // 生成するリンクのタイプ。この場合は ?page=2 のようなリンクを生成する。
];

こういうデータを渡してあげることで、実際のデータフェッチ内容とは無関係のページング UI を生成できます。

f:id:ikasamak503:20190309234948p:plain
{:end} がちゃんと計算されていてえらい

しかし無意味なページング UI を構築しては使い物にならないので、フェッチしたデータとの整合性をとる必要があります。 今回、データフェッチは API 化して HTTP の JSON でレスポンスを返してくれる実装になりました。 なので、データと横並びでページングのパラメータをセットし、それをそのまま横流しする形をとりました。 気を付ける点として、 API 側は CakePHP 3 のため、若干パラメータのキーが変わっています。 以下、 CakePHP2 => CakePHP 3 の形式です。

  • order => sort *2
  • limit => perPage

この変換は CakePHP 3 側に実装しました。 こういった互換性対応は新旧どちらに実装するかは好みによるところですが、新 : 旧 = 2 : 4 という事情もあって新側に実装しました。 *3

余談

今回の CakePHP 2 => CakePHP 3 移行でこういうのも作りました。 レガシーと戦う全国 5000 億人の CakePHPer のためにも、また別の機会に紹介できたらなと思います。

  • Paginator でフェッチしたときに明示的にページングパラメータをセットするのが面倒なので自動化
  • CakePHP 3 の Entity を CakePHP 2 の配列形式と型に変換

まとめ

  • $this->request->params['paging'] にパラメータを渡すことで PaginatorHelper を操れます
  • CakePHP 2 <=> CakePHP 3 の Paginator のパラメータには一部互換性がないので気を付けよう

参考

*1:もちろんユニットテスト、UI 自動テストなんで気の利いたものはありません

*2:今回は order を使っていないので、ここの互換性は確かめてないです

*3:いずれ捨てることになるので旧側に作ってそのまま捨てたいという気持ちもある

可処分時間を上手に分配する

概要

  • 一日のうち、自由時間をうまく使いたい
    • 今の自分の環境だと平日 2 時間、休日 13 時間
  • 自由時間を可視化したい
  • 自由時間から自動計算してやりたいことを割り当てたい
    • 日々取り組みたいこととそれぞれの割合あるいは固定時間を定義しておく
    • そういう Web サービスとかスマートフォンアプリないかな?
    • ないなら作るか・・・
  • 自由時間を増やしたい

背景

やりたいことはいくつかあるんだけど、「知る」ことが大好きなぼくは自由な時間をほとんどそれにつぎ込んでしまっている。 特に、簡単に情報にアクセスできるネットサーフィンをやりがちだ。 Twitter の TL やリストを眺める、ブログや Qiita を読み漁る、 Wikipedia で知らない単語を延々とはしごする、はてブ人気エントリーをチェックする。 さらには読み切れないから「あとで読む」に入れて、次の日は他の新しい記事を読んでいたりする。 「あとで読む」があとで読まれない。無限に積みあがっていく。

今日、このブログを書いたのはある意味では奇跡で、ある意味では自分への警告だ。 今から時間の使い方を変えなければ、無限に「知」をむさぼるだけの妖怪になってしまう。 かつてそれでもいいと思っていた時期もあったが、今はそう思っていない。 自分を変えねば。

考察

自由時間 = 24 時間 - 生活維持時間

  • 生活維持時間とは?
    • 仕事と通勤
    • 睡眠、食事、入浴といった生理的なもの
    • ルーチンワーク
      • 掃除や家電、住環境のメンテナンス
      • 子供がいたら送迎とか

まずは自由時間を見えるようにする

  • 生活維持時間から逆算する
  • 生活維持時間を見えるようにする
    • 仕事と通勤: 12h ( 定時勤務でこれはアレな気がする )
    • 睡眠: 7h ( 最低これだけは欲しい )
    • 食事: 1h ( これは朝+夜だけ、昼は仕事時間に含まれる )
    • 入浴: 1h ( ぬるま湯で長風呂が好き )
    • その他: 1h ( 掃除とか、アトピーのケアとか、雑に使うバッファ )
  • 24h - 22h ( 生活維持時間 ) = 2h ( 自由時間 )
    • 少なくね・・・?
    • 2h 残業するだけで消える
    • まず通勤時間が 3h でアレ。通勤は悪。
  • 休日は 13h あった
    • 仕事と通勤がなくなって自由時間 +12h
    • 睡眠を +1h して 自由時間 -1h。休日はたくさん寝たいよね。

自由時間のスケジューリング

  • やりたいことを決める
    • インプット
      • インターネット
      • 書籍
    • アウトプット
      • ブログを書く
      • コードを書く
    • 語学 ( 特に英語 )
    • ゲーム ( 最近はモチベーション低い )
  • やりたいことの時間配分を決める
    • 割合: インプット 50%, アウトプット 30%, ゲーム 20% とか
    • 固定時間: インプット.書籍 は 0.5h/1日 とか
  • こういうのを日々自動計算して教えてくれると嬉しい
  • イレギュラーなイベントで予定が狂っても現在時間から再計算してくれると助かる
  • 何に何時間使ったとか、時間の記録もできると良い

自由時間を増やす

  • = 生活維持時間を削る
  • 安易に短時間睡眠に挑戦して睡眠時間を削りがち
    • やってもいいけど向き不向きがあるのでその方法に固執しないこと
  • 通勤時間は本当に無駄でしかないので削りたい
  • 個人的には入浴、その他は頑張れば削れそう
    • 体のケアや QoL 維持、日々の時間に余裕を持たせたいのでできれば削りたくない
  • 仕事の時間を削るのもありかもしれない

結論

  • まずは手動で、自由時間、生活維持時間の可視化とスケジューリングをやってみる
  • そういうことを計算・記録してくれるアプリや Web サービスを探す
  • なかったら自分で作る

docker-compose で複数環境を構築するときの設定をなるべく DRY に書く

概要

  • docker-compose-f, --file オプションを複数使って、共通の Composeファイル と環境ごとの Compose ファイルを読み込むようにします
    • こうすることで、共通の設定を DRY に書けます
  • -p, --project-name オプションと networks で環境を分離します
  • 以下のリファレンスの内容が理解できていればこの記事を読まなくても大丈夫です

docs.docker.com

docs.docker.com

背景

docker-compose, 便利ですよね。 Docker 完全に理解した *1 くらいのレベルで複数コンテナの環境を作るなら手軽でよいです。

その docker-compose が開発環境だけならまだいいんですが、 テスト用の環境も同じ仕組みで作るようになると、 環境差分をどうするかが課題になります。 例えば、以下のような環境差分が考えられます。

  • コンテナに渡す環境変数が違う
    • Web サーバの Virtual Host
    • 外接システムのエンドポイント
  • 開発環境とテスト環境で必要なサービスが違う
  • TLS の通信だったら証明書が違う

まだまだありそうです。 実際に差分が発生するかはアプリケーションの設計や環境にもよるんですが、 いったんこういう差分があり得るという前提で話を進めます。

アンチパターン

1. Compose ファイルは 1 つで、環境ごとに branch を切る

  • 修羅の道です
  • Compose ファイルがブランチごとに成長して、目も当てられなくなります
  • 唯一のメリットは、「環境ごとの起動コマンドが一緒」
    • でも各環境にそれぞれ 1 step で起動できるジョブを用意しておけばいいですよね?

2. 環境ごとに Compose ファイルを作る

  • 修羅の道パート 2 です
  • やっぱり Compose ファイルがファイルごとに成長していきます
  • プロジェクト名が同じだから同じサービス名が使えなくなり、かなりつらいです

解決策

$ docker-compose -f docker-compose.yml -f <your_env>.yml -p <your_env> up -d

実装例はこちら。

github.com

解説

-f, --file オプション

共通の Compose ファイル ( docker-compose.yml ) と環境依存の Compose ファイル ( <your_env>.yml ) を読み込みます

共通

$ cat docker-compose.yml
version: "3.5"
services:
  web:
    build:
      context: ./web
    volumes:
      - ./web/proxy-to-back.conf:/etc/nginx/conf.d/proxy-to-back.conf:ro
    environment:
      VIRTUAL_HOST: "*.web.local,*.back.local"
    networks:
      - default
      - front
  back:
    build:
      context: ./back
    depends_on:
      - web

networks:
  front:
    external: true

環境依存

$ cat env1.yml
version: "3.5"
services:
  web:
    environment:
      VIRTUAL_HOST: "env1.web.local,env1.back.local"
      APP_ENV: env1
    volumes:
      - ./web/env1.html:/usr/share/nginx/html/index.html:ro
  back:
    volumes:
      - ./back/env1.html:/usr/share/nginx/html/index.html:ro

networks:
  default:
    name: env1
$ diff env1.yml env2.yml
5c5
<       VIRTUAL_HOST: "env1.web.local,env1.back.local"
---
>       VIRTUAL_HOST: "env2.web.local,env2.back.local"
7c7
<       - ./web/env1.html:/usr/share/nginx/html/index.html:ro
---
>       - ./web/env2.html:/usr/share/nginx/html/index.html:ro
10c10
<       - ./back/env1.html:/usr/share/nginx/html/index.html:ro
---
>       - ./back/env2.html:/usr/share/nginx/html/index.html:ro
14c14
<     name: env1
---
>     name: env2
  • 前のファイルで定義した同じフィールドの項目が後のファイルにあれば、それを上書きします。
  • 新しい値があれば追加します。

例えば、

  • environment の同じキー ( VIRTUAL_HOST ) は上書きされます
  • environment の異なるキー ( APP_ENV ) は追加されます
  • volumes は追加されます

最終的にどんな設定になるのかは、 docker-compose config コマンドを使うと見れます。

$ docker-compose -f docker-compose.yml -f env1.yml -p env1 config
networks:
  default:
    name: env1
  front:
    external: true
    name: front
services:
  back:
    build:
      context: /home/ikasamak/work/dc-multi-env/back
    depends_on:
    - web
    volumes:
    - /home/ikasamak/work/dc-multi-env/back/env1.html:/usr/share/nginx/html/index.html:ro
  web:
    build:
      context: /home/ikasamak/work/dc-multi-env/web
    environment:
      APP_ENV: env1
      VIRTUAL_HOST: env1.web.local,env1.back.local
    networks:
      default: null
      front: null
    volumes:
    - /home/ikasamak/work/dc-multi-env/web/proxy-to-back.conf:/etc/nginx/conf.d/proxy-to-backi.conf:ro
    - /home/ikasamak/work/dc-multi-env/web/env1.html:/usr/share/nginx/html/index.html:ro
version: '3.5'

-p, --project-name オプション

プロジェクト名を指定します。 デフォルトは compose ファイルのあるディレクトリ名です。

~/work/dc-multi-env master* $ docker-compose up -d
Creating network "dc-multi-env_default" with the default driver

プロジェクト名を指定せずに同じディレクトリで別環境を立ち上げると、 同プロジェクトの同サービスと見なされ、既存のコンテナがかき消されてしまいます。

$ docker-compose -f docker-compose.yml -f env1.yml up -d
Creating network "env1" with the default driver
Creating dc-multi-env_web_1 ... done
Creating dc-multi-env_back_1 ... done
$ docker-compose -f docker-compose.yml -f env2.yml up -d
Creating network "env2" with the default driver
Recreating dc-multi-env_web_1 ... done
Recreating dc-multi-env_back_1 ... done
$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                            NAMES
ffd5b9f636be        dc-multi-env_back   "nginx -g 'daemon of…"   5 minutes ago       Up 5 minutes        80/tcp                            dc-multi-env_back_1
6dcafba12120        dc-multi-env_web    "nginx -g 'daemon of…"   5 minutes ago       Up 43 seconds       80/tcp                            dc-multi-env_web_1

-p でプロジェクト名を指定し、別環境として立ち上げます。

$ docker-compose -f docker-compose.yml -f env1.yml -p env1 up -d
Creating network "env1" with the default driver
Creating env1_back_1 ... done
Creating env1_web_1  ... done
$ docker-compose -f docker-compose.yml -f env2.yml -p env2 up -d
Creating network "env2" with the default driver
Creating env2_back_1 ... done
Creating env2_web_1  ... done
$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                            NAMES
2496c6858e0c        env2_web            "nginx -g 'daemon of…"   4 seconds ago       Up 3 seconds        80/tcp                            env2_web_1
fa5bcec301ad        env2_back           "nginx -g 'daemon of…"   5 seconds ago       Up 4 seconds        80/tcp                            env2_back_1
0aca011671e7        env1_web            "nginx -g 'daemon of…"   12 seconds ago      Up 11 seconds       80/tcp                            env1_web_1
ec885d01fa73        env1_back           "nginx -g 'daemon of…"   13 seconds ago      Up 12 seconds       80/tcp                            env1_back_1

プロジェクト名を指定する方法

  • -p, --project-name オプションを使う
  • COMPOSE_PROJECT_NAME を使う

Compose ファイルにプロジェクト名を指定できれば楽なんですけど、そういう仕様にはならなかったようです。 まあ普通は環境ごとにディレクトリ分けるから、 .env で何とかしなさいということなんでしょう。

github.com

networks

適切にネットワークを設定しないと、コンテナ名で名前解決していると別環境にトラフィックが飛んでしまうことがあります。

例えば、面倒なんで全部 front のプロキシのいるネットワークにつないでしまえ! ということをすると

$ cat docker-compose.yml
version: "3.5"
services:
  web:
    build:
      context: ./web
    volumes:
      - ./web/proxy-to-back.conf:/etc/nginx/conf.d/proxy-to-back.conf:ro
    environment:
      VIRTUAL_HOST: "*.web.local,*.back.local"
    networks:
      - front
    depends_on:
      - back
  back:
    build:
      context: ./back
    networks:
      - front

networks:
  front:
    external: true

env1webenv1, env2 両方の back とつながるので、 back へのアクセスがロードバランシングされてしまいます。

$ curl -H "Host: env1.back.local" localhost
here is env2.back!
$ curl -H "Host: env1.back.local" localhost
here is env1.back!
$ curl -H "Host: env1.back.local" localhost
here is env2.back!
$ curl -H "Host: env1.back.local" localhost
here is env1.back!

なので、適切にネットワークを設定しましょう。 プロジェクト名を分けているのであれば、 default で通信するようにしましょう。 *2

おまけ

extends を使えば、設定をモジュール化して再利用できるようです。

docs.docker.com

2019/03/08 追記

extends は compose file format v3 で使えなくなってました。

docs.docker.com

まとめ

  • 共通部分、環境依存部分に分けることで Compose ファイルを DRY に書けます
  • -p オプションでプロジェクト名を分け、同じサービス名を別環境で同時に動かせるようにします
  • networks を適切に設定して別環境にトラフィックが迷い込まないようにします

参考

*1:https://twitter.com/ito_yusaku/status/1042604780718157824

*2:サービス名だけの名前解決 ( http://back とか ) は、default ネットワークから行われるようです