読者です 読者をやめる 読者になる 読者になる

Shin x Blog

PHPをメインにWebシステムを開発してます。Webシステム開発チームの技術サポートも行っています。

WordPress Way を知る本 「エンジニアのための WordPress 開発入門」

著者の杉田さんに献本して頂いたので拝読しました。

エンジニアのためのWordPress開発入門 (Engineer's Library)

PHP エンジニアのための WordPress 本

タイトルにあるとおり、普段フレームワークなどを使って Web システムを開発している PHP エンジニアのための WordPress 本です。

ただ、ユーザとして WordPress を利用するのではなく、PHP エンジニアとしてどのように扱うのか、どのように付き合うのか視点で書かれているので、興味深く読むことができました。

序盤で、おおっと思ったのが、第 2 章です。WordPress のインストールにの次に紹介されているのが、いきなり、WP_DEBUG をはじめとしたデバッグモードに関する定数、Debug Bar プラグイン、コマンドラインツールの WP-CLI、そして開発用仮想環境を構築する VCCW という流れになっています。

PHP エンジニアが、WordPress を触るための環境構築といった感じで、このあたりからも誰に向けた本なのかが分かります。

個人的には、さらに PhpStorm 上で WordPress を触るための設定などに進んでいくと、グッときますね :)

第5章 WordPress の基本アーキテクチャ

本書のキモとなるのは、第5章の「WordPressの基本アーキテクチャ」でしょう。

WordPress のファイル構成、データ構成(データベーステーブル)、基本処理の流れ、HTTP リクエストパラメータの扱い、フックの仕組みなどベースとなるアーキテクチャが解説されています。

これらを知らずとも WordPress は利用できまずが、ブラックボックスとして扱うより、基本的な流れを把握して利用すれば動作を想像することができ、問題解決やプラグインによる拡張を行う際に大きな助けとなるでしょう。

まず、この章を読んで全体を把握してから、知りたい箇所を各章で補っていくのが良さそうですね。

WordPress と向き合うための本

WordPress は、歴史の長いプロダクトのため、現在行われている PHP 開発とは異なる流儀でコードが実装されています。

コードを読んだ人がまず感じることが、まさに本文に書かれています。

ところが、率直のところ、WordPress のソースコードは美しいとはとても言えません。重要なメソッドや関数が恐ろしいほどの行数だったり、驚くほどたくさんのグローバル変数が定義されていたりします。

こうした WordPress のコードを見た時に感じた違和感をはっきりと明言しておき、それを前提としてどう向き合うのかという視点が本書のユニークなところです。

これは本書に随所で見られる視点で、WP_Query が 4,000 行超あることが書かれていたり、サンプルコードに global キーワードがあったり、グローバル変数一覧がまとめられていたりします。

「言いたいことは分かった。ただ、WordPress のコードを向き合うなら、こうしよう。」

と言ってくれているような気がします。

さいごに

「郷に入っては郷に従え」というのは、フレームワークでも言語でもプラットフォームでも良く言われていることです。

WordPress で開発するなら、まずは WordPress Way に従うのが良い方法で、本書にはその方法がまとめられています。WordPress Way を知るには大きな足がかりになる貴重な本です。*1

開発で WordPress を活用する人はもちろんのこと、WordPress の内部構造や動きに興味がある方にもおすすめです。

エンジニアのためのWordPress開発入門 (Engineer's Library)

エンジニアのためのWordPress開発入門 (Engineer's Library)

  • 作者: 野島祐慈,菱川拓郎,杉田知至,細谷崇,枢木くっくる
  • 出版社/メーカー: 技術評論社
  • 発売日: 2017/01/26
  • メディア: 単行本(ソフトカバー)
  • この商品を含むブログを見る

*1:個人的には、WordPress はブラックボックスにして、WP REST API で触るというのも面白いと思いますが、その視点でも本書は役立ちます。

「PHPの現場」という Podcast をはじめます

学生の頃からラジオを聞いて育ってきたので、技術系のPodcastが好きで色々と聞いています。 日本語で PHP 界隈の話題について話すPodcastが無かったので、はじめてみることにしました。

「PHPの現場」というPodcastです。

PHP がメイントピックにはなりますが、PHP の開発現場で出てきそうな話題なら、それ以外の技術や話題についても扱っていくつもりです。

私がお話したいゲストの方をむかえて対談(雑談)形式でざっくばらんに話していきたいので、色々な方にお声がけしていこうと思います。その際は、よろしくお願いしますm(_ _)m

Podcast配信環境

Podcastを配信する上で、どうしようかと迷ったのは配信環境です。

SoundCloud などのサービスを利用することも考えたのですが、どうせなら作ってしまおうと思い、自作しました。(実は、過去にPodcastの配信を行ったことがあり、その時も自作(GAE+Python)していました。)

Podcastキャスト配信サーバは、ようは RSS フィード配信とオーディオファイル配信(Range Requests 対応)ができれば良いので実装は難しくありません。ただ、配信 RSS の内容を調整したり、どうせ趣味で作るならとあれこれいじっていたら、思ったより時間がかかってしまいました。

音源はあるのに、配信サーバの調整に時間をかけるという本末転倒な状況でしたが、久しぶりに趣味の Web サービスを作ったので、空き時間に楽しんで作業していました :) 自由にできるアプリケーションがあるのはいいですね。

技術要素としては、以下のような感じです。

  • Digital Ocean
  • Docker / docker-compose
  • PHP 7.1 + Slim 3
  • papertrail(サーバ、Docker コンテナのログを集約)
  • Google Analytics(Web アクセス、RSS / オーディオファイルダウンロード実績を集約)
  • Mackerel(サーバ監視)

開発でも本番でも docker-compose でコンテナを起動するだけなので、インフラはその時々の状況でよしなに移動できます。

今回の実装でアプリケーション仕様は固まったので、今後は気が向けば別言語で再実装して遊ぶのもいいですね。

初回配信

初回は、下記エントリに書いた「Podcast で話すような感じでやる発表」を実現した昨年末の関西 PHP 勉強会の模様を配信しています。

blog.shin1x1.com

@tanakahisateru さんと例外について話しました。

この回は、年末ということもあり、乾杯の後に発表をはじめた、くだけた雰囲気でした。途中、二人以外にも話に入ってきたりして、楽しかったです。配信を聞かれる方は、そういった雰囲気での話というのをイメージして下さい :)

さいごに

まだ手探り感が満載ですが、はじめて見ないことには分からないので、少しづつやっていこうと思います。

感想や要望などあれば、 #phpgenba で tweet して頂けると嬉しいです。

それでは、「PHPの現場」よろしくおねがいしますー!

PHPの現場

PHPの現場

  • Masashi Shinbara
  • テクノロジー
  • ¥0

openssl_encrypt() による PKCS#7 パディング

PHP の openssl_encrypt() にて、ブロック暗号による暗号化を行うと PKCS#7 パディングが行われます。この動きを確認してみます。

http://php.net/manual/ja/function.openssl-encrypt.php

PKCS#7 パディング

ブロック暗号では、決められたブロック長を単位として暗号化を行います。対象となる平文がブロック長の倍数の長さであれば良いのですが、そうではない場合、不足分を補う必要があります。これがパディングです。*1

PKCS#7 パディングは RFC 5652 で定義されたパディング方式です。補ったバイト数を 1 バイトの値として埋めます。例えば、3 バイトを補うのであれば、0xXXXX030303 のように 3 が 3 つ並びます。PKCS#7 では、1 バイトで不足バイト数を示すので、1 - 255 バイトまでを補うことができます。

01     <--- 1 バイト分
0202   <--- 2 バイト分
030303 <--- 3 バイト分

https://en.wikipedia.org/wiki/Padding_(cryptography)#PKCS7

これと似た動きをするのが、PKCS#5 パディングで、こちらは 8 バイトを倍数とした場合のパディングを行います。つまり、取り得る値は 1 - 8 となります。

余談ですが、Java の javax.crypto.Cipher にある PKCS5Padding は、名前は PKCS5 ですが、実質は PKCS#7 相当の動きをするようです。

他のパディングには、0 で埋める ゼロパディングなどがあります。

openssl_encrypt() によるパディング

openssl_encrypt() のデフォルトでは、PKCS#7 パディングが行われます。

この動きを見ていくためのサンプルが以下です。このサンプルでは、openssl_encrypt() で暗号化、openssl_decrypt()で復号を行っています。

ここでは、AES(Rijndael)を利用しているのでブロック長は 128bit(16バイト)です。平文は plain の 5 バイトなので、不足分の 11 バイトがパディングで埋められます。

<?php
$plaintext = 'plain';
$key = hash('sha256', 'this is a secret key.');
$method = 'aes-256-cbc';
$iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length($method));

$encrypted = openssl_encrypt($plaintext, $method, $key, 0, $iv);
printf("encrypted: %s\n", $encrypted);

$decrypted = openssl_decrypt($encrypted, $method, $key, 0, $iv);
printf("decrypted: %s\n", $decrypted);

これを実行すると下記のような出力になります。暗号文はデフォルトで base64 エンコードされています。復号した結果を見ると元の平分が出力されています。ここではパディングが見えないですが、これは openssl_decrypt() にて復号時にパディングを除去するためです。

encrypted: vGZiiLXzuiGnOBkJbWAiHw==
decrypted: plain

暗号文にパディングが含まれているかを確認するために復号時に OPENSSL_NO_PADDING を指定して、パディング除去が行われないようにします。

<?php
$plaintext = 'plain';
$key = hash('sha256', 'this is a secret key.');
$method = 'aes-256-cbc';
$iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length($method));

$encrypted = openssl_encrypt($plaintext, $method, $key, 0, $iv);
printf("size: %d bytes\n", strlen(base64_decode($encrypted)));

$decrypted = openssl_decrypt(base64_decode($encrypted), $method, $key, OPENSSL_RAW_DATA | OPENSSL_NO_PADDING, $iv);
printf("decrypted: %s\n", bin2hex($decrypted));
printf("size: %d\n", strlen($decrypted));

これを実行したのが下記です。

パディングが見えるように復号した結果は bin2hex() で出力しています。前半の 706c61696e の部分は平文の plain です。それにつづく 0b が 11 回連続している箇所がパディングです。ブロック長 16 バイトから平文の 5 バイトを引いた 11 バイトがパディングとなるので 0x0b が 11 回連速しています。

復号した結果は 16 バイトとなっており、これもブロック長と一致します。

encrypted: iwBv6P3euwwdct8DHutBNA==
decrypted: 706c61696e0b0b0b0b0b0b0b0b0b0b0b
size: 16

平文サイズがブロック長と一致している場合

では、平文サイズがブロック長と一致している場合はどうなるでしょう。

平文を plainplainplaina の 16 バイトにして実行したのが下記です。

復号結果は、なんと 32 バイトになりました。内容を見ると 10 が 16 回連続しています。これは、ブロック長分のパディングです。つまり、1 ブロック分が追加されているわけです。

平文の後にはパディングが付いていることを前提とするため(そうでないと平文なのかパディングなのか判断できない)にこのような動きになっているのでしょう。

encrypted: PFZ8G+XYhs9x22aP/a7Jy88eQJv6GVaKMXrBfq5/bGU=
decrypted: 706c61696e706c61696e706c61696e6110101010101010101010101010101010
size: 32

さいごに

openssl_encrypt() によるパディングを見てきました。

デフォルトのまま、openssl_encrypt() で暗号化し、openssl_decrypt() で復号するならパディングを意識する必要はあまりありません。

しかし、別システムで暗号化されたものを復号したり、その逆の場合は、どのような形式でパディングを行うのかというのは共有しておく必要があります。

参照

*1:実際には、後述するとおりブロック長倍数であってもパディングが追加されます。

アプリケーションから例外を投げる派、投げない派

例外をどのような状況に投げるかもしくは投げないか、というのはわりと意見が分かれるところです。もちろん、プログラミング言語によっても異なりますが、同じプログラミング言語ユーザ同士でも様々です。

基本の考え方

ベースとしては、Effective Java の項目 39 にある下記の方針が参考になります。

例外的な状況の時にのみ例外を使う。

Effective Java

禅問答のような定義ですが、これには異論は無いでしょう。例外を正常フローで利用したり、制御構造に用いるべきではありません。

人によって異なるのは「例外的な状況」の解釈です。

例外的な状況

この「例外的な状況」の解釈は人によって異なるようで、これまでも議論になっていました。これまで聞いた解釈を乱暴に分けると以下の 2 パターンに分かれます。

1. アプリケーションから独自の例外を投げる派

ランタイムやミドルウェア連携などプラットフォーム起因のエラーだけではなく、 アプリケーションやドメインにおけるエラーを例外としてスローする派です。例外クラスも必要に応じて定義します。

例外をエラー処理に積極的に利用する考え方ともいえ、例えば、入力値におけるバリデーションエラーやドメインロジックのエラーにおいても例外を利用します。

2. プラットフォームやフレームワークが投げる例外以外は使わない派

ランタイムやミドルウェア連携などプラットフォーム起因のエラーなど深刻なエラーでのみ例外を利用し、アプリケーションにおけるエラーにおいては例外を利用しない考え方です。

この考え方の人の中には、例外クラスはアプリケーション独自で定義せず、アプリケーションからは例外をスローしない(ランタイムやフレームワーク、ライブラリからの例外のみ扱う)という意見もありました。

アプリケーションのエラーは、戻り値で通知することになります。(特殊な値(null や false)を使ったり、原因別に戻り値を返すなど)

アプリケーションから独自の例外を投げる派の意見

私は、前者の「例外を積極的に使う派」です。理由としては、下記のようなメリットを感じています。 (なお、ここでは PHP を対象として、検査例外のことは考えていません。)

  • [実装][利用] エラー処理を例外に統一できる。
  • [利用] 呼び出す側は例外だけ対応すれば良い。(catch する / 対応しない(上位にスロー))
  • [利用] どのレイヤでエラー処理するかを呼び出し側が選べる。(上位レイヤに伝播できるので、フレームワークの統一的なエラー処理に任せてしまうなどが簡単)
  • [実装] エラー時は例外を投げれば良いので、ガード節の代わりにもなる。
  • [実装] テストでは、@expectedException でテストを書けば良いので、何のテストか分かりやすい。(正常時は例外飛ばないので)
  • [利用] try / catch を書けば、try の中は正常系、catch の中は異常系と処理が分かれて分かりやすい。
  • [利用] 戻り値のエラー判定が不要。例外が飛ばなければ正常終了と判断できる。
  • [利用] 例外クラスが持つ情報量が多い。(特にスタックトレース)

さいごに

例外については、人それぞれ思いがあるようで、議論になりやすいところです。この機会に色々な人の意見を聞いてみたいですね。

今日開催の関西 PHP 勉強会でも、話しましょう。

https://kphpug.doorkeeper.jp/events/55554

EFFECTIVE JAVA 第2版 (The Java Series)

EFFECTIVE JAVA 第2版 (The Java Series)

追記

Twitter へ頂いたコメントをまとめました。色々な意見がありますね。

togetter.com

はてぶでも多くのコメントがあったので、こちらも追記。

b.hatena.ne.jp

子どもの iPhone SE に行った設定リスト 2016年12月

子ども(13 才以上)用に iPhone を購入したので、セットアップしたメモです。

iPhone

IIJmio を利用するために、Apple Store で SIM フリーの iPhone SE を購入しました。

www.apple.com

SIM

私が、IIJmio を利用していて、何ら不都合が無いので、同じく IIJmio にしました。連絡は、LINE(チャット or 電話)で行うつもりなので、データ通信専用 SIM を申し込みました。

3GB プランで、900円/月です。(後述する i-フィルター for マルチデバイス を申込んだので、+ 360円)

www.iijmio.jp

ペアレンタルコントロール(機能制限)

利用できるアプリやアクセス先を制限できる機能です。必要な箇所を設定しました。

support.apple.com

Apple ID

利用者が、13 才未満なら子ども用の Apple ID が作成できます。

support.apple.com

今回は、13 才を超えているので、Google アカウントを作成し、Gmail のアドレスで、Apple ID を取得することにしました。

インストールするアプリを許可制にする

iCloud のファミリー共有を使えば、子どもが自由にアプリをインストールすることができなくなります。 アプリをインストールすると、親(管理者)へ承認を求めるようになります。承認が要求されると、親の iOS デバイスに通知が来て、ここで許可 or 拒否を指定できます。

www.apple.com

support.apple.com

ファミリー共有すると、家族の端末にインストールしたアプリや現在位置を管理者の端末で確認できます。反対に管理者の情報を家族の端末で見ることもできますが、どの情報を共有するかは ON / OFF できるので、管理者のものは共有しないことも可能です。

support.apple.com

Apple Music

Apple Music を家族で利用できるようにします。すでに個人メンバーシップには登録していたので、これをファミリーメンバーシップに切り替えて、子どもの Apple ID を招待しました。

support.apple.com

support.apple.com

LINE

LINE アカウントを作成するには電話番号か Facebook アカウントが必要になります。今回は、データ通信のみの SIM にしたので、Facebook アカウントを作成しました。

LINE アプリをインストールして、「Facebook アカウントでログイン」を選ぶと、Facebook連携でアカウントで LINE アカウントが作成できます。

official-blog.line.me

あと、意図せず友達申請が来たりするのを防ぐために幾つか設定しておきます。

  • プロフィール - 「IDで友達追加を許可」を off(年齢確認できていないので、off のはず)
  • プライバシー管理 - 「メッセージ受信拒否」を on (友達以外からのメッセージ受信を拒否)
  • 友だち - 「友だち追加」を off (アドレス帳の連絡先を自動で友だちに追加しない)
  • 友だち - 「友だちへの追加を許可」を off (電話番号を保有している LINE ユーザが自動で友だち追加したり、検索できるのを拒否)
  • タイムライン - タイムラインは使わなくて良いけど、もし使うなら「友だちの公開設定」「新しい友だちに自動公開」を確認。

i-フィルター for マルチデバイス

フィルタリングソフトを一応導入しました。まだ、どれほど活用できるかは分からないですが、やってみて、不要なら解約する予定です。

www.iijmio.jp

さいごに

メールアドレスも無い、Apple ID も無い状態から iPhone を設定したのは、はじめてなので思ったより手間取りました。

ある程度慣れている人なら調べながら自分でできますが、詳しく無い人だとほんと大変ですね。

格安 SIM を利用する人が増えていますが、3 キャリアにはこうしたセットアップを店舗でサポートしてくれるサービスがあるので、月々の通信費は割高でも、サポートが必要な人にとってはそちらの方が良いのかもしれません。

はじめに手にした iPhone 。大事に使って、良い思い出を作っていって欲しいものです。

公開 Podcast のような勉強会セッションをやってみる

動機

  • 内容が大事で、それが聞きたいだけなのに、スライド作ってとか表現への準備が大変。
  • 技術系勉強会では、スライドを作ることは本質ではない。(内容が伝われば手段は何でも良い)
  • かといって、登壇する時にスライド無しで一人で話すのも大変。
  • 運営としても、発表者としても前々から解決したいテーマだった。
  • Podcast は、音声だけだが、十二分に伝わる。緩い雰囲気も聞きやすい。
  • あ、これだ!

考えていること

  • 話し手と聞き手がいて、二人が話すのをみんなで見る。
  • 話し手が、公開しているコンテンツ(blog、発表スライド、OSS、tweet 等々)をベースに話していく。
  • 話し手は、過去に公開したものがベースなので、スライド作るより準備が楽。当日も聞き手がいる方が話しやすい。
  • 場合によっては、コード書いて見せたりするのもあり。
  • 聞き手は、事前に質問を考えておく必要がある。負担を双方で受け持つ。

期待していること

  • 聞きたいことを聞きたい人に負担を少なめで聞ける。
  • 懇親会で話すような雰囲気でくだけた話をしたい。(「懇親会が本番」を本編で)
  • 参加している人も、二人が対話している方がリラックスして聞きやすいし、入りやすいのでは。
  • 間があってもいいし、二人いるから、どちらかが話せば良い。
  • パネルディスカッションに比べると、各人に話を振っていく必要が無いから楽。

さいごに

こんなセッションを 12/27 開催の関西 PHP 勉強会でやってみます。

どうせなら、録音して Podcast とかで出せるといいかなとかも考えてますが、予定は未定なので、ご都合が付く方は参加お待ちしています!

Amazon Dash Button にみるドメイン特化クラスのヒント

この投稿は、PHP Advent Calendar 2016 - Qiita の 7 日目です。

DDD のようなドメインを意識した開発手法でなくても、ドメインコンテキストで必要な操作のみを実装したクラスを作ってみましょう話です。

Amazon Dash Button

Amazon Dash Button は、ボタンが一つだけあるデバイスです。このボタンを押すと、あらかじめ決められた商品の注文が Amazon に送信され、商品が配送されるというシンプルなものです。

サントリー天然水 Dash Button

このデバイスは、「商品を届けて欲しい」というユースケースに対して、ボタンを押すというシンプルなインターフェイスを実装しています。単にボタンを押すだけなので、その裏側でどのようにして実現されているかを知らずとも、誰もが利用できます。違う言い方をすれば、ボタンを押す(商品を注文する)ことしかできないので、操作方法を覚える必要もなく、想定外の使い方もやりようがありません。*1

このように、ドメインやそのユースケース、関心事に特化したインターフェイスを用意すれば、そのコンテキストの中では迷うことなく、自然に理解でき、扱い方を間違えることもありません。

ドメイン特化クラス

「ユーザに現在の年齢を尋ねる」というユースケースの実装を考えてみます。

User クラスとして実装したのが、下記です。

現在の年齢を算出するには、生年月日が必要となるので、コンストラクタで渡しています。生年月日は、cakephp/chronos パッケージの Date クラスで表現しています。

年齢を尋ねるには、age メソッドを利用します。

<?php
namespace Acme\Generic;

use Cake\Chronos\Date;

class User
{
    /**
     * @var Date
     */
    private $dateOfBirth;

    /**
     * @param Date $dateOfBirth
     */
    public function __construct(Date $dateOfBirth)
    {
        $this->dateOfBirth = $dateOfBirth;
    }

    /**
     * @return int
     */
    public function age(): int
    {
        return $this->dateOfBirth->age;
    }
}

このコードを実行するのが、下記です。生年月日をコンストラクタを渡して、age メソッドを呼ぶだけなのでシンプルです。

<?php
// 現在日を設定
// Chronos::setTestNow(Chronos::create(2016, 12, 8, 12, 34, 56));

$user = new User(Date::create(2000, 1, 1));
echo $user->age(), PHP_EOL; // 16

このままでも動作は問題は無いのですが、気になるのは、Date クラスです。これは、日付を扱うクラスなので、挙動としては問題ありません。ですが、このクラスは汎用クラスなので、ドメインのコンテキストで扱う生年月日ではありません。プロパティ名を見れば、生年月日であることは想像できますが、より明示するために生年月日を示すクラスを実装します。

生年月日を示したのが、下記の DateOfBirth クラスです。内部表現としては、Date クラスを利用しているので、ただのラッパーのようにも見えます。ここで大事なのは、Date クラスのインスタンスを内包して、DateOfBirth クラスでは、年齢を算出する age メソッドのみを実装しているという点です。これにより、DateOfBirth クラスが担うのは、年齢を算出するだけということが分かります。

<?php
namespace Acme\Domain;

use Cake\Chronos\Date;

class DateOfBirth
{
    /**
     * @var Date
     */
    private $date;

    /**
     * @param Date $date
     */
    public function __construct(Date $date)
    {
        $this->date = $date;
    }

    /**
     * @return int
     */
    public function age(): int
    {
        return $this->date->age;
    }
}

DateOfBirth クラスを使った User クラスは下記です。コンストラクタの型宣言が変わっているだけなのですが、Date クラスに比べると生年月日であることが読み取れます。もちろん、User クラス内で生年月日を操作する際も age メソッドしかできないので、間違いようがありません。

もし、配送日などの別の日付クラスが存在したとしても、誤って User クラスに与えることができません。要求されている仕様が分かりやすいだけでなく、誤った引数に対するガードとしても有効です。

<?php
namespace Acme\Domain;

class User
{
    /**
     * @var DateOfBirth
     */
    private $dateOfBirth;

    /**
     * @param DateOfBirth $dateOfBirth
     */
    public function __construct(DateOfBirth $dateOfBirth)
    {
        $this->dateOfBirth = $dateOfBirth;
    }

    /**
     * @return int
     */
    public function age(): int
    {
        return $this->dateOfBirth->age();
    }
}

ドメイン特化クラス実装のポイント

このようなドメインに特化したクラスを実装するには色々な方法がありますが、いくつかポイントを書きます。

操作を必要最低限に絞る

PHP には、数多くの組み込みクラスやライブラリ、フレームワークがあります。一般的なデータ型を示すためのクラスは探せば見つかると思うので、多くの場合、それをベースとして利用するでしょう。

その場合、継承ではなく上記の DateOfBirth クラスのように委譲を使って実装します。

例えば、DateOfBirth クラスを Date クラスを継承して実装したとします。この時、DateOfBirth クラスは、Date クラスの public / protected メソッドを受け継ぐので、Date クラスと同様の操作が可能です。Date クラスには、日付の加算、減算といったメソッドがありますが、このコンテキストの生年月日に関してはそういった操作は不要です。不要なだけでなく、うっかり使ってしまうと想定外の動作を招く場合もあります。

こうした違いは、IDE で開発すると明確です。委譲版 DateOfBirth は、age メソッドのみ補完候補となります。一方、継承版では、age メソッドに加えて、Date クラスのメソッドが多数出現します。一つしかボタンが無いのと、たくさんのボタンがあるのと、どちらが利用者として分かりやすいかは一目瞭然です。

  • 委譲版 DateOfBirth

f:id:shin1x1:20161207135012p:plain

  • 継承版 DateOfBirth

f:id:shin1x1:20161207135019p:plain

対象に特化する(汎用性を求めない)

上記、継承が使われる理由として、DRY を意識しすぎるため、継承で実装の再利用を行いたいというものがあります。

例えば、このシステムに配送日といったモデルがあったとしましょう。生年月日も配送日も日付を示すものなので、Date クラスを基底クラスとし、それぞれ継承したとします。しかし、生年月日と配送日を同種のものとして扱う場面というのは、現実世界ではあまり思い当たりません。

ドメインに特化したクラスの実装では、汎用的に実装する必要はなく、対象にのみフォーカスして実装します。

これは、一般的な汎用クラスとは指向している方向が異なります。あくまでドメインに特化したクラスなので、汎用性を考える必要はありません。*2

もし、実装を共有したいという目的であれば、共通処理を行う別クラスを実装して、そこに委譲するか、トレイトにまとめるという手法を取ると良いでしょう。

一つの概念を表す小さなクラスを作る

今回の生年月日のように、一つの概念のみを示すクラスを作るようにします。凝集度が高い小さなクラスを作ることで、実装しやすく、使いやすく、テストしやすいクラスになります。

こうしたクラスは取り回しが楽なので、どのようなアーキテクチャであっても、取り入れることができます。特に、DDD のようなドメインを意識したアーキテクチャでなくても、一部だけでも、一つの概念だけでも、簡単に取り込むことができます。

実際の開発では、ValueObject(値オブジェクト)として実装するところから始めると良いです。

コンテキストによって異なる

DDD でも「境界付けられたコンテキスト」があるように、コンテキストというのは重要な概念です。

同じ「生年月日」でも、本エントリで扱う「生年月日」と別のシステムで扱う「生年月日」では、あるべき振る舞いが異なるかもしれません。今回は年齢の算出のみを実装しましたが、「今日が誕生日かどうか」「今月が誕生月か」といった振る舞いが必要となる場合もあるでしょう。むしろ、この2つのみが関心事であれば、「年齢を算出する」は不要かもしれません。

このように、同じ用語、概念であっても、コンテキストによって求められるものは異なります。つまり、生年月日とはこうあるべきといった汎用の生年月日クラスを実装したとしても、それがそのまま適用できるかどうかは、コンテキストによって異なるということです。

ユースケース

こうしたドメインに特化したクラスを少しづつ導入したい場合は、ValueObject として導入するのが実装するのが良いでしょう。ValueObject にすれば、操作が限定できるだけでなく、オブジェクトの不変条件を内包したり、内部表現をカプセル化することができます。

例えば、下記のようなものが考えられます。

  • 日付(日付ライブラリは高機能なわり、ドメインで要求されるものは一部。)
  • 数値、特に演算が行われるもの(金額、ポイントなど。演算の限定、制約条件の付与。)
  • 要求仕様が明確なデータ(商品コードなど。)

それ以外でも、サービスのように状態を保持しない処理もドメイン特化クラスで実装しやすい箇所です。

例えば、暗号処理なら、実際の暗号化や複合処理は別クラスで実装し、後はユースケース別のクラスからこれらのクラスを委譲で利用して、暗号化や複合を行うという場合に使えます。暗号処理は、暗号化と複合をペアで行う場合が多いので、ベアごとにクラスを分けると、ここで処理で暗号化したものは、あそこで複合しているというのが分かりやすくなります。

さいごに

こうしたドメインに特化したクラスを作るというのは、一見面倒なように見えますが、やってみると面白いものです。

生年月日も、汎用日付オブジェクトで表現すれば、それで終わりです。一方、生年月日クラスを実装すると、生年月日にどのような振る舞いが求められているかを考えることになります。このような視点で仕様を見ていけば、一つ一つの用語にも関心がいき、より深くドメインを理解して、コードに表現するようになります。

これは、教条的なものではなく、むしろ感覚に近いもので、こうしたプラクティスを重ねていくことで、身についていくもののように思います。

ドメインに必要な部品を少しづつ作っていき、それを組み合わせて構築していくという作業は、安心感もあり、とても楽しい作業です。身近なところから、取り組んでみてはどうでしょうか。

blog.shin1x1.com

*1:誤って押して、想定外の注文が飛ぶことはありますが

*2:ドメインコンテキスト内で汎用性を持たせるということは有り得ます。