いよいよ寒くなってきた。今日は調子が良く無さげだった。出勤したがあまりやる気は出なかった。
RabbitMQ にメッセージを送る処理を以前書いてたが、障害時などでコネクションが張れなかった際の処理を考えていなかった。マイクロサービスアーキテクチャを採用した場合は、各連携しているサービスの死は"雨の日のシナリオ"のユースケースとして考慮するべきだと思った。しかしこれがやっかいだった。例外の設計がわからなかったのだ。わからないなりに考えた僕の設計書いてみたい。
RabbitMQ を使うために php-amqplib を使っていて、これをインフラ層の MessagingServiceInterface を実装した RabbitMQService でラップしている。MessagingServiceInterface を定義することで RabbitMQ から ZMQ への変更を、そんな変更があるかないかは別として、やりやすくしたのだった。さて、RabbitMQ が今障害で死んだとして、このときにメッセージキューイングをすると php-amqplib は実行時例外を吐く。これが問題だった。この実行時例外はあくまでも php-amqplib 依存のものであって、あるいは RabbitMQ に依存のものであって、このような具体的な実装依存の例外を MessagingServiceInterface を実装したクラスが投げるべき例外では無いと思ったのだ。そこで MessagingService\RuntimeException を定義して、php-amqplib が 実行時例外を投げた場合はキャッチしてこれを投げるようにした。
ここまでは良いように思う。だって php-amqplib の実行時例外をインフラ層でキャッチせず放置すれば、もはや MessagingServiceInterface によってカプセル化されたこのクラスの具体的な実装は誰にも分からず、投げられる例外をキャッチできず、いずれプレゼンテーション層まで達し、ユーザに無慈悲な 500 番エラーを返すのみである。
インフラ層で具体的な実装から切り離されて抽象的な実行時例外として再定義された例外は、インフラ層のクライアントであるアプリケーション層で渡される。RabbitMQ が死んでいるというこの事象は正に"雨の日のシナリオ"であり、雨の日のシナリオは確か以前ユースケース工藤開発という本を読んだ時に例外として実装すべきものと読んだ気がするので、アプリケーション層の例外としてまたしても再定義されるのだうか。だから、php-amqplib.RuntimeException => MessagingService.RuntimeExeption => Application.MessagingSytemRuntimeException のような感じで例外が上層へ伝わっていく?なんだか冗長だぜ、という気もしなくないのだが、金曜日に出勤なので、同僚と相談したい。