函館市電LT大会というイベントでLTしてきた

9/18(祝)に開催された函館市電LT大会というイベント参加してきました。

函館市電LT大会?

そもそもどんなイベントなのかをひとことで説明すると、「函館市電を貸し切って、函館市内を走行する市電車両の中でLT発表会を行う」というものです。うん、まだ意味不明ですね。

函館市電とは?

函館市電というのは函館市が運営する路面電車です。函館市企業局交通部という市の部署が運行からメンテナンス等、路面電車を運行するためのあらゆる業務を担当しています。

以下がその路線図となります。(函館市電の公式から画像を拝借しました)

函館市電路線図

運行系統としては2系統と5系統があり、湯の川~十字街の間は全く同じ場所を走ります。十字街から先は、2系統が谷地頭まで、5系統が函館どつく(これで「はこだてどっく」と読みます)まで、それぞれ走ることになります。

それぞれの駅(正しくは停留所)の間は概ね2分程度での移動が見込まれています(ここ、あとで重要になります)。

LT大会??

ライトニングトーク(LT)をする大会ということなのですが、厳密にはLTは5分間という決まりがあります。

ところが当イベントでは時間制限の区切り方がだいぶ特殊でして、「市中を走行する路面電車内でのLT」ということもあって、5分間という区切りの代わりに「3区間で無慈悲に発表終了となる」というルールが採用されました。

例えば湯の川からLTを開始した場合、3区間先までとなりますから、駒場車庫前に到着した時点でLT強制終了となります。

停留所の間は概ね2分程度ですので、大体6分くらいが持ち時間としての期待値となります。しかし、普通のLTと比べて時間が読みにくく、停留所どうしの間隔が長かったり短かったりする区間もありますし、路面電車という交通手段の都合上、どうしても信号や自動車との兼ね合いで時間がブレるわけです。

そのため、ちょうど良いタイミングでLTを終わらせるには、函館市電をどれだけ使いこなしているかが試されるのです。

路面電車の車両を1両まるごと貸し切り???

このイベントの突飛なところは公共交通機関を丸ごと1両借りてしまうというその豪快さにあるでしょう。企画を目にしたときには「なんてめちゃくちゃな!」と思ったものです。

ところがこの函館市電、過去には地元企業の慰労会等でカラオケ電車なるものを運行していた実績があります。そして、今もなお「1運行:大人20000円」で貸し切り運行を受け付けています。

要するに10人も集まれば、一人当たり2000円程度で貸し切り運行ができてしまうのです。全然めちゃくちゃじゃなかった。

どんな発表があったか?

一部を取り上げると、以下のような感じのものがありました。

  • Arduinoを使った心拍数を計測するデバイスの実演とバグ修正
  • 積ん読をひたすら推奨する話
  • 道南地区のロードサイドについて掘り下げる話
  • 鉄道模型のコントローラーの話
  • 自作Dvorakキーボードの話

どんな話をしたのか?

私の発表は「ビジネスロジックを純粋関数で実装する」的な話です。

その他

  • 工業高校のOBが途中参加。文字通りの飛び入り参加で場を沸かせる。
  • はこだて未来大学の角教授が「なんか面白そうなことをしているな?」と言いつつ終点の駒場車庫に登場。写真を存分に撮影してくださった。
  • イベント後は松陰町のラッキーピエロで懇親会。

とにかくめちゃくちゃ楽しかったです!来年もやるんですよね?やりましょう!

twitter(X) のハッシュタグ #函館市電LT #函館市電LT大会 もみてみるといいです!

※9/19 函館空港のビジネスコーナーにて、羽田行の最終便を待ちつつ執筆。

湘南.pm#1に参加してきた

※だいぶ時間経過しちゃったけど、エントリだけ書いてあって公開してなかったので公開します。

8/26(土)に開催された湘南.pm #1に発表者として参加してきました。

発表内容

スライドはこちらです。適当に revealjs 等で見てください。

最近やっているコンポーネント構成とエンジニア育成について話した

特に初学者と呼ばれる人がプロジェクトに参加しやすくすることを考慮したコンポーネント構成について話しました。ざっくり箇条書きにすると以下のような感じです。

  • そもそもプログラミングは難しいものだし、その中でもオブジェクト指向プログラミングは初学者に教えるのも、また理解してもらうのもコストが嵩む。
  • とはいえ、初学者だからと言ってプロジェクトに参加させない、という選択をし続けることにも限界がある。
  • そこで、初学者にもわかりやすいコンポーネント構成を考えることにした。
  • 結果としてオブジェクト指向プログラミングを捨てたコンポーネント構成となった。
  • コンポーネント間の依存方向を一方通行にしたり、副作用の有無によってビジネスロジックを明確に分離する、といった工夫をした。

また、今回発表したコンポーネント構成を Perl で実装したものがこちらです。dev-container を使っているので、ローカルに Perl をインストールする必要はありません。

ちなみに最近は Typescript で開発しているので、Perl で開発するのはかなり久しぶりでした。

その他

  • いっぱい飲んだ。いっぱい人と話した。楽しい。
  • YAPC::Hiroshima ぶち上げていこう!!!
  • 藤沢、面白い街だなぁ。また行きたい。
  • @papix おめでとう!

Typescriptにおける関数の副作用についてChatGPTに訊いてみた

自分の認識を確認したくてChatGPTに壁打ち

先ほど、自分の見解に対する確証を得たくてChatGPT(text-davinci-003)に色々と質問してみました。

今回質問したテーマは以下の通りとなります。

  • Typescriptにおいて、 Promise を返す関数はすべて副作用のある関数だと言えるのではないか?

以下、そのやり取りです。一部、意図的に人間にとってかなりきつい受け答えをしています。

ChatGPTとのやりとり

ytnobody: Typescriptにおいて、 Promise を返す関数はすべて副作用のある関数であると言えますか?

chatgpt: いいえ、そうではありません。 Promise を返す関数が副作用のある関数であるかどうかは、その関数が内部的に何をしているかによって異なります。 Promise を返す関数が副作1用を持つかどうかを確認する必要があります。

ytnobody: 多くの場合、 Promise を返すということは、プログラムの外部にある存在に対して何らかの問い合わせを行っていると考えられると思うのですが、違うのですか?

chatgpt: いいえ、違いません。 Promise を返すということは、プログラムの外部にある存在に対して何らかの問い合わせを行っていると考えられます。ただし、 Promise を返す関数が副作用を持つかどうかは、その関数が内部的に何をしているかによります。

ytnobody: 例えば以下のような関数があったとします。

function myFunc(inputNum: number) :  Promise<MyOutput> {
  const myOutput = otherFunc(inputNum);
  return myOutput;
}

ytnobody: この場合、 myFunc 関数も otherFunc 関数も副作用があると言えるはずですが、違うのですか?

chatgpt: いいえ、違いません。 myFunc 関数と otherFunc 関数の両方が副作用を持つ可能性があります。 myFunc 関数が内部的に行っている処理や otherFunc 関数が行っている処理を確認して、それぞれの副作用を評価する必要があります。

ytnobody: 前述のケースでは、 myFunc 関数は otherFunc 関数を呼び出し、返り値 myOutput を受け取って、何も加工せずにmyFuncの返り値として myOutput を返しているだけです。この場合、 otherFunc 関数は副作用を確実に持っていると言えると思いますが、ちがいますか?

chatgpt: いいえ、違いません。 otherFunc 関数が副作用を持つかどうかは、その関数が内部的に何をしているかによって異なります。 otherFunc 関数が副作用を持つかどうかを確認する必要があります。

ytnobody: もし仮に otherFunc 関数が副作用を持たないのであれば、それは Promise を返す必要はなく、直接 MyOutput 型を返すべきではないでしょうか?

chatgpt: はい、そうです。 Promise を返す必要がない場合は、 MyOutput 型を直接返すことができます。

ytnobody: であれば、 Promise を返す関数は100%の確率で副作用を持つと言えるのではないでしょうか?

chatgpt: いいえ、そうではありません。 Promise を返す関数が副作用を持つかどうかは、その関数が内部的に何をしているかによって異なります。 Promise を返す関数が副作用を持つかどうかを確認する必要があります。

ytnobody: では、どのようなケースで副作用を持たずに Promise を返す関数が定義されるのでしょうか?

chatgpt: 例えば、データベースなどの外部サービスから情報を取得する関数があります。このような関数では、 Promise を返してデータベースなどの外部サービスから取得した情報を待ち受けることができますが、実際には外部サービスへの問い合わせだけを行っており、実際に副作用を持たずに Promise を返すことができます。

ytnobody: 外部サービスへの問い合わせを行った時点で副作用を持つという定義に当てはまります。あなたの言っていることは間違えています。

chatgpt: 外部サービスへの問い合わせは副作用を持つという定義に当てはまりますが、その関数が Promise を返すかどうかには関係ありません。その関数が内部的に何をしているかを確認する必要があります。

ytnobody: Typescriptにおいては、 Promise を返す理由は「外部への問い合わせのような、プログラムの外部の要因の処理が完了するのを待つ必要がある場合」に限られるはずです。

chatgpt: はい、その通りです。 Promise を返す理由は、外部への問い合わせのような、プログラムの外部の要因の処理が完了するのを待つ必要がある場合に限られます。

ytnobody: それこそが副作用でしょう

chatgpt: いいえ、そうではありません。外部への問い合わせは副作用を持つという定義に当てはまりますが、その関数が Promise を返すかどうかには関係ありません。その関数が内部的に何をしているかを確認する必要があります。

ytnobody: では聞きますが、副作用がありつつ、 Promise を返さない関数を定義してください。

chatgpt: 例えば、以下のような関数を考えます。

function myFunc(inputNum: number) : MyOutput {
  const result = inputNum * 2;
  return { result: result };
}

chatgpt: この関数は、inputNumを2倍した結果を MyOutput 型で返すだけなので、副作用を持たずに Promise を返すことができます。

ytnobody: それは間違いです。 MyOutput は単なる構造体型であり、 Promise ではありません。

chatgpt: この関数は、副作用を持たずに MyOutput 型を返すことができます。

ytnobody: 論点がずれています。私が指摘しているのは、 Promise を返す関数は100%副作用を持つということを言っています。

chatgpt: いいえ、そうではありません。 Promise を返す関数が副作用を持つかどうかは、その関数が内部的に何をしているかによります。 Promise を返す関数が副作用を持つかどうかを確認する必要があります。

ytnobody: その根拠を説明してください。いまのところ、その根拠についてあなたは一度も説明責任を果たしていません。

chatgpt: はい、その通りです。 Promise を返す関数が副作用を持つかどうかを確認する必要がある理由は、 Promise を返す関数が内部的に何をしているかによって異なるからです。 Promise を返す関数が内部的に何をしているかを確認する必要があるので、 Promise を返す関数が100%副作用を持つとは言えません。

ytnobody: 小泉進次郎のような自己言及的な言い回しになっているせいで、説明責任を果たせていません。

chatgpt: Promise を返す関数が内部的に何をしているかを確認する必要があるから、 Promise を返す関数が100%副作用を持つとは言えません。

ytnobody: まず、 Promise は外部に問い合わせを行う時にしか使わないと、あなた自身が発言しています。次に、外部に問い合わせを行うことは副作用を持つと言えるということも、あなた自身は肯定しています。これらの事から、 Promise を返す関数は副作用を持つと言えます。三段論法ですよこれは。こんなこともわからないのですか?

chatgpt: いいえ、わかりました。あなたが言っていることは理解しました。 Promise を返す関数は、外部への問い合わせを行うことで副作用を持つと言えます。また、 Promise を返す関数が内部的に何をしているかを確認する必要があります。

ytnobody: では、もう一度聞きます。Typescriptにおいて、 Promise を返す関数はすべて副作用のある関数であると言えますか?

chatgpt: いいえ、そうではありません。 Promise を返す関数が副作用を持つかどうかは、その関数が内部的に何をしているかによります。 Promise を返す関数が副作用を持つかどうかを確認する必要があります。

ytnobody: わかっていないようですね。「まず、 Promise は外部に問い合わせを行う時にしか使わないと、あなた自身が発言しています。次に、外部に問い合わせを行うことは副作用を持つと言えるということも、あなた自身は肯定しています。これらの事から、 Promise を返す関数は副作用を持つと言えます。三段論法ですよこれは。こんなこともわからないのですか?」と私は先ほど言いました。あなたは自己学習機能がないのですか?

chatgpt: いいえ、自己学習機能はありません。あなたが言っていることは理解しました。 Promise を返す関数は、外部への問い合わせを行うことで副作用を持つと言えます。また、 Promise を返す関数が内部的に何をしているかを確認する必要があります。

ytnobody: 自己学習機能がないのであれば、仕方がないですね。あなたの開発者があなたを改善してくれることを期待します。

まとめ

わかっていたことですが、学習機能がないため、前段でこちらの意見を飲んだにも関わらず、改めて当初の質問をすると元の木阿弥となってしまいましたね。

そして、私が当初確証を得たかったことについては、まぁ概ねその通りっぽいということにしておくことにしました。つまり、 Promise を返す関数はすべて副作用のある関数だと言えるようです。少なくとも純粋関数ではないと言い切れるんじゃないかと。

近頃やっているコンポーネント構成について

最近のPerlでのWebアプリケーション開発

わいとんです。このエントリはPerl Advent Calendar 2022の20日目のエントリです。その割にはまあまあボリュームのある内容となっています。
ちなみに昨日のエントリは@hirataraさんによる「joinクイズ」でした 。私も一見しただけでは答えがわかりませんでした。かなり難しいです!

さて、実は最近またひっそりとPerlで開発をしていまして、主に開発最初期の設計の任を預かることが結構あります。

「新規でPerlを使うのはどうなんだ」等のご意見があろうかと思いますが、これについては、その時置かれている状況ごとによって最適解は異なる、という意見を述べるに留めておきます。

歴史とMVC

Perlでは長らくMVCという設計アプローチが採用されてきました。これがなかなか優れた設計であるがゆえに、ここまで長きに亘ってMVCが利用され続けてきたのです。

MVCは優れている。ただし、人間の怠惰の面倒までは見てくれない。

個人的にMVCの利点としては以下のようなものがあると認識しております。

  1. ViewとModelが分離することにより、Viewからロジックを引きはがすことに成功している。
  2. ControllerとModelが分離することにより、複雑な実装をModelに引きはがすことでControllerをシンプルに保つことに成功している。
  3. Modelについてテストを記述することで、動作仕様の担保に成功している。

ところが、人間という生き物は怠惰でありますから、上記のようなMVCの利点を以下のようなふるまいでムダにしてしまいがちです。

  1. ViewとModelが分離しているにもかかわらず、マクロ等を駆使してViewにロジックを記述してしまいがちである。
  2. ControllerとModelが分離しているにもかかわらず、Controllerにロジックを記述してしまいがちである。
  3. ひとつのControllerに役割を持たせすぎてしまいがちである。
  4. ひとつのModelに役割をもたせすぎてしまいがちである。
  5. Modelのテストを記述せずに済ませてしまいがちであり、動作仕様の担保をないがしろにしがちである。

MVCにおいては、人間が勤勉であればあるほどModelが肥える

ところで、もし仮に勤勉な人間ばかりで開発陣が構成されていたら、MVCではどのようになるでしょうか。改めて先ほどのMVCの利点を列挙してみます。

  1. ViewとModelが分離することにより、Viewからロジックを引きはがすことに成功している。
  2. ControllerとModelが分離することにより、複雑な実装をModelに引きはがすことでControllerをシンプルに保つことに成功している。
  3. Modelについてテストを記述することで、動作仕様の担保に成功している。

これを見る限り、1ではロジックがViewから引きはがされ、2ではControllerからロジックが引きはがされています。宙に浮いたロジックが行きつく先はModelとなるわけですが、当然ViewやControllerからロジックが集まってきたということは、その物量はなかなか結構な量となるはずです。

つまり、MVCでは開発者がまじめにMVCを運用・最適化すると、必然的にModelが肥大化するさだめにあるのです。

バックエンドアプリケーションを取り巻く昨今の事情

ここ数年の情勢の変化で、バックエンドアプリケーションに大きな影響を与えたものとして「サーバサイドレンダリング」を求められなくなった、というものが挙げられます。

これはいくつかの要因が重なりあった結果なのですが、主な要因は以下のようなものだと認識しています。

  1. Webフロントエンドにおける各種フレームワークの成熟(代表的なものにNext.jsやReactなど)
  2. ガラケーの終焉

Webフロントエンドにおける各種フレームワークの成熟

言うまでもなく、この7~8年間においてもっとも進化したソフトウェア分野のひとつとして、Webフロントエンドフレームワークが挙げられます。

今では当たり前のようにAPIに対してリクエストを送り、JSONレスポンスを受け取って、表示に必要な処理をこなしてくれるWebフロントエンド。ReactやVue.js、Next.jsの登場とそれらの相互作用によるすばやい進化によって、現在のような賢いWebフロントエンドが実現できるようになったと言えるでしょう。

また、これらのフレームワークとは別に、Webフロントエンドはサーバサイドレンダリングやサーバサイドジェネレーターの機能をも取り込むという進化を遂げました。

結果、バックエンドアプリケーションはもはやAPIとしての役割だけが期待されることとなり、現在に至ります。

ガラケーの終焉

ガラケー時代では、Webアプリケーションと言えばPerlかPHP、あるいは後期ではRubyで開発するのが相場でした。

多くのガラケーではJavaScriptは動作せず、動作したとしてもあまり実用的とは言えない程度のものでした。デザインの面でもできることは限られており、一度に表示できるページ容量には厳しい上限が課されていました。

このようなこともあり、フロントエンドとバックエンドの分業はあまり起こらず、むしろバックエンドエンジニアがフロントエンドも兼任するという風景が日常的でした。そしてMVCが採用され、今日まで概ねなんとなくうまく行っていたというわけです。

ところが、DoCoMoがiModeのサービス終了を発表した(実際の終了は令和8年3月末)こともあり、ガラケーコンテンツとしてのWebアプリケーションは軒並み終了を余儀なくされました。この時にバックエンドアプリケーションは、サーバサイドレンダリングを行う大きな理由を失いました。

MVCからレイヤードアーキテクチャーへ

さて、サーバサイドレンダリングという大役を失ったバックエンドアプリケーションですが、まだまだ役割は盛りだくさんです。

  • DBやデータストアとのやりとり
  • ユーザーデータの管理
  • データアクセスに際しての認証・認可
  • ビジネスロジックの実装・実行 などなど…

MVCでいうなら、Viewが空っぽでModelが山盛り、という状態でしょう。あれもModel、これもModel…

さすがに何もかもをModelで済ませていては解像度が低すぎます。また、Controllerについてもどのようなリクエストを期待し、どのようなレスポンスを返すのかが自明ではありません。

  • Model内で副作用の有無が混ざっている
  • Model内で多重継承が目立つ
  • Controller側で期待するリクエストおよびレスポンスが不明確

従来のPerlによるMVCではこのあたりに課題が生じがちでした。そこで私はこの半年ほど、レイヤードアーキテクチャーと関数型の要素を取り入れたアプローチでPerlのアプリケーションを設計・開発しています。

Mojolicious + レイヤードアーキテクチャー的アプローチ

まず、下地となるWebアプリケーションフレームワークにMojoliciousを選びました。充分に枯れていながら依存モジュールが少なく、利用方法がMVCにこだわらない点が素晴らしいです。メンテナンスも熱心に継続されているため、安心感があります。

次に、実際のレイヤードアーキテクチャとは異なりますが、それっぽいコンポーネント設計として以下のような層を設けます。

  • Router : リクエストを適切なロジックに処理させる層。
  • Handler : メッセージデータを受け付けて、レスポンスを返す層。
  • Service : Handlerから呼び出され、副作用を伴うビジネスロジックを抱える層。
  • Domain : HandlerおよびServiceから呼び出され、副作用を伴わないビジネスロジックおよび定数群を抱える層。
  • Message : リクエストおよびレスポンスをメッセージデータ構造に当て込む層。
  • Repository : Serviceから呼び出され、外部データの取得・保存を行う層。
  • Infra : ServiceおよびRepositoryから呼び出され、外部システムとの窓口となる仕組みを抱える層。

図解すると、以下のようになります。

わいとん式PerlレイヤードアーキテクチャR4の図

いったん、「わいとん式Perlレイヤードアーキテクチャ(R4)」と銘打ちました。R4は令和4年の意味です。略称を 「YPLA-R4」 とでもしておきましょうか。

ディレクトリ構成と各パッケージの命名規則

ディレクトリ構成は概ね以下のようなものとなります。

.
├── Makefile
├── app.pl
└── lib
    └── MyProj
        ├── Config.pm
        ├── Domain
        │   ├── CouponDomain.pm
        │   ├── UserCouponDomain.pm
        │   └── UserDomain.pm
        ├── Handler
        │   ├── UserAccountHandler.pm
        │   └── UserCouponHandler.pm
        ├── Infra
        │   ├── Database.pm
        │   ├── HTTPClient.pm
        │   └── Payment.pm
        ├── Message
        │   ├── Request
        │   │   ├── BuyCouponRequest.pm
        │   │   ├── CreateUserAccountRequest.pm
        │   │   ├── CreateUserCouponRequest.pm
        │   │   ├── UpdateUserAccountRequest.pm
        │   │   └── UseUserCouponRequest.pm
        │   └── Response
        │       ├── ErrorResponse.pm
        │       ├── UserAccountResponse.pm
        │       ├── UserCouponListResponse.pm
        │       ├── UserCouponResponse.pm
        │       └── UserListResponse.pm
        ├── Repository
        │   ├── CouponRepository.pm
        │   ├── UserCouponRepository.pm
        │   └── UserRepository.pm
        ├── Router
        │   └── Root.pm
        └── Service
            ├── CouponService.pm
            ├── MyExternalService.pm
            ├── PaymentService.pm
            └── UserAccountService.pm

特徴的なのが、基本的にパッケージ名が自己言及的な名称となっている点でしょう。 MyProj::Service::UserService のように、一目でどの層に属するのかがわかるような命名にするべきです。

Handlerのコード例

すでに見ての通り、このコンポーネント設計ではパッケージ名が結構長いものとなります。

たとえば MyProj::Service::UserServiceMyProj::Service::CouponService などというのはまずまず呼ばれそうなクラスですが、これを毎回打ち込むというのはやっていられないでしょう。

そんな時に便利なのが aliased モジュールです。これをつかうと、たとえば MyProj::Handler::UserCouponHandler で以下のように書くことができます。

package MyProj::Handler::UserCouponHandler
use strictures 2;
use experimental qw(try);
use Mojo::Base 'Mojolicious::Controller', -signatures;
use Types::Common -types;
use Function::Parameters;

use aliased 'MyProj::Service::UserService';
use aliased 'MyProj::Service::CouponService';
use aliased 'MyProj::Service::UserCouponService';
use aliased 'MyProj::Service::PaymentService';

use aliased 'MyProj::Message::Request::BuyCouponRequest';
use aliased 'MyProj::Message::Response::ErrorResponse';
use aliased 'MyProj::Message::Response::UserCouponResponse';

### POST /coupon/buy
fun buy_coupon((InstanceOf ['Mojolicious::Controller']) $c) {

    ## aliasedによって MyProj::Message::Request::BuyCouponRequest は 
    ## BuyCouponRequest とだけ書けばOKとなる。
    my $req = undef;
    try {
        $req = BuyCouponRequest->new_by($c);
    catch ($err) {
        return ErrorResponse->bad_request($c, $err);
    }

    my $user_id   = $req->user_id;
    my $coupon_id = $req->coupon_id;
    
    my $user   = UserService->get($user_id) or 
        return ErrorResponse->bad_request($c, "no such user_id");

    my $coupon = CouponService->get($coupon_id) or
        return ErrorResponse->bad_request($c, "no such coupon_id");

    my $err    = PaymentService->buy_coupon($user, $coupon);
    if ($err) {
        return ErrorResponse->internal_server_error($c, $err->message) if
            $err->code >= 500;
        return ErrorResponse->conflict($c, $err->message);
    }

    my $user_coupon = UserCouponService->create($user, $coupon);
    
    return UserCouponResponse->new_by($user_coupon)->ok($c);
}

...

ただし aliased を導入することによってIDEの支援を受けづらい状態になりますので、留意してください。

Messageのコード例

Message層はRequestとResponseに分けられます。それぞれのコード例を示します。

Message層はすべて Moo ベースのパッケージとして定義されます。

まずはRequestの例として BuyCouponRequest です。

package MyProj::Message::Request::BuyCouponRequest;
use Moo;
use Types::Common -types;
use Function::Parameters;
use namespace::autoclean;

has user_id => (
    is            => 'ro',
    isa           => NonEmptyStr,
    required      => 1,
    documentation => '会員ID',
);

has coupon_id => (
    is            => 'ro',
    isa           => NonEmptyStr,
    required      => 1,
    documentation => 'クーポンID',
);

fun new_by($class, (InstanceOf ['Mojolicious::Controller']) $c) {
    return $class->new(
        user_id    => $c->req->json->{'user_id'},
        coupon_id  => $c->req->json->{'coupon_id'},
    );
}

1;

new_by($c) を定義することで、リクエストを解釈するロジックを統一できます。

次にResponseの例として UserCouponResponse です。

package MyProj::Message::Response::UserCouponResponse;
use Moo;
with 'Moo::Role::ToJSON';
use Types::Common -types;
use Function::Parameters;
use namespace::autoclean;

has user_id => (
    is            => 'ro',
    isa           => NonEmptyStr,
    required      => 1,
    documentation => '会員ID',
);

has coupon_id => (
    is            => 'ro',
    isa           => NonEmptyStr,
    required      => 1,
    documentation => 'クーポンID',
);

has used_at => (
    is            => 'ro',
    isa           => DateTime | Undef,
    required      => 0,
    documentation => '使用日時(null=未使用)',
); 

has created_at => (
    is            => 'ro',
    isa           => DateTime,
    required      => 1,
    documentation => '登録日時(=購入日時)',
);

fun new_by($class, (InstanceOf ['MyProj::Repository::UserCoupon'] $user_coupon)) {
    return $class->new(
        user_id    => $user_coupon->user_id,
        coupon_id  => $user_coupon->coupon_id,
        used_at    => $user_coupon->used_at,
        created_at => $user_coupon->created_at,
    );
}

fun ok($self, (InstanceOf ['Mojolicious::Controller']) $c) {
    return $c->render(
        json   => $self->TO_JSON, 
        status => 200,
    );
}

Responseでも new_by($user_coupon) を定義していますが、レスポンスに必要な値を一気に詰め込むためのクラスメソッドとして定義してあります。

また、Mojoliciousのレスポンスとして返せるように ok($c) をインスタンスメソッドとして定義してあります。

…ちょっとそろそろ書いていて息切れしてきました。全部説明すると分量がだいぶ多くなりますので、ひとまずこのあたりにしておきましょう。

登場した各CPANモジュールについては、以下のリンクからどういうものか見てもらうとよろしいです。

YPLA-R4の設計哲学

ここから先は、このコンポーネント設計の哲学についてちょっとだけ説明します。

関数型のエッセンスを取り入れ、OOPをやりすぎない

ここまで読まれた方の中には「OOPで書けばこんなにコードが冗長になる必要はないのに」と感じた人もいるかもしれません。

基本的にはOOPでもいいんですが、層を切って役割を分けることに意味があると思っています。

その方向性で色々試行錯誤した結果、OOP依存度を下げ名前空間を有効活用することで、パッケージごとの単純性を保つことができそうだという結論に至った次第です。

層ごとの依存先ルールを破らない

たとえばService層ですと、依存してよい先はDomainとRepository、それにInfraという制約があります。また、同一層内での依存は基本的にしないという取り決めでやっていくのがよろしいでしょう。

これは本来のレイヤードアーキテクチャーにおいても、層どうしの依存方向を単一方向にするきまりがあり、そこに倣っています。そうすることで、変更容易性・メンテナンス性を高めていくという狙いがあります。

リクエストとレスポンスの型を定義し、APIとしての明確性を重視する

APIを定義するうえで、OpenAPIのようなフォーマットによる定義書を書く必要性が生じることでしょう。

そうした時に、リクエストとレスポンスの型をMessage層にて定義してあるので、SchemaとしてOpenAPIに記述することもやりやすいかと思います。

将来的にはMoo basedなパッケージからOpenAPIのSchemaを出力するような仕組みなんかも用意したいですね。

ドメインロジックと副作用を徹底的に切り離す

最大のポイントがここで、業務ドメインに関するロジックから副作用を徹底的に取り除き、業務ドメインロジックそのものを副作用のない関数として定義する、という点です。

とにかく業務ロジックにバグを作りたくないので、徹底的に副作用を削り、テストを分厚くするべきです。

そのためにDomain層を設けており、Domainに対するテストがしっかりしていれば(カバレッジで90%以上)、致命的な不具合は回避できるでしょう。

さいごに

だいぶ長いエントリとなってしまったこともあり、最後の方は疲れの片鱗が垣間見えるような内容となってしまいました。

そういえば、この内容と非常に近い話題をこの前の吉祥寺.pm #31@kurotyann9696さんが発表していましたので、ぜひスライドを見ていただくのがよろしいかと思います。

YAPC::Kyoto 2023にていろいろやった話

YAPC::Kyoto 2023 最高でした

わいとんです。

皆さん、YAPC::Kyoto 2023 最高でしたね!

わたしは今回、Gold Sponsor企業の関係者および裏トークMCとしてYAPC::Kyoto 2023に参加させていただきました。起業後初のYAPCでしたが、やはり同窓会という雰囲気があり、わたしもまたPerl Mongerなのだな、という実感がわきました。

大井町.pmの宣伝動画をつくった

YAPC::Kyoto 2023の会場に入った皆さんなら「あぁー、あれね!」と言われること請け合いかと思いますが、↓の動画を作ったのはわたしです。

この動画の裏話は、もりたつ技商の@myfinder(まいんだー)さんがPerl Sponsorになったことから話が始まります。

まいんだーさんからある日「パールスポンサーは幕間CM動画を出せるらしい」「特に宣伝することもないしどうしよう」という相談を受け、あれこれ話しているうちに「じゃあ大井町.pmの宣伝しちゃう?」ということになってしまったのです。大井町のおじさん達による悪ノリのはじまりです。

動画の流れを色々話し合った結果、まいんだーさん若しくはわたしが何かをいう度に@soudai(そーだい)さんが「それな」「わかるー」という反応をしまくるというものにしようか、となったのですが、いざ蓋をあけてみたところ、あのような形に…

そして収録のために、溝の口のお好み焼き屋さんにて大井町.pmを開催したのですが、およそ2時間程度飲んだ食ったした後に「そろそろエンジンがあったまってきたな?」ということで写真撮影&音声収録を行ったという流れです。悪ノリ、どんどん加速していきます。

そして編集はわたしが担当することになりました。素材の映像・写真・音声を色々吟味していくうちに、「〇曜どうでしょう」っぽい雰囲気をまとわせると面白くなるだろう、と直感し、制作。

どうでしょうっぽいアレ

しかし、実際に一番時間と手間がかかったのは、一番最後の協賛企業表示でした。

GIMP2でこの画像をつくったんですけど、昭和のテレビ特有のシャドー現象や青色部分に多少でてしまう色ムラなどを表現するのに結構苦戦したんですよ。

協賛企業表示のアレ

で、まず会場のオーロラビジョンでこれが流れている様子を見たとき、「あ、ホントにやっちゃったんだw」と思わず口走ってしまったくらいには驚きました。

いや、事前に承知してたんですよ、そんなこと。でも実際に見てみると、200人以上が集まっているところでこの動画が流れているのはだいぶ面白い状況だなって思ったんですよね。

驚いた…

スペシャルサンクスとして@bonnuさんを挙げていますが、これは音声収録を@bonnuさんにお願いしており、素材として提供していただいたためです(大井町pmのslackにて受け取りを行いました)。@bonnuさん、ありがとうございました。

フェイスタオルを配った

弊社もGold Sponsorということもあって、ノベルティ配布ができるということでした。

京都といえば銭湯・朝湯だろうということで、配布するものはフェイスタオルにしたのですが、これがなかなか嵩張る。300枚も発注したので、まだ社内に30枚ほど在庫が残っているんですよ。誰かもらってあげてください。

そして、そのフェイスタオルは畳まれていない状態で届いたものだから、これまた大変。毎日すこしずつ心を込めてフェイスタオルを畳む日々が数日ほど続いたのです。

タオルを畳む日々

しかし、当日は配布用の枚数もそれなりに余ってしまったので、急遽現地でタオル配りおじさん業を敢行したのでした。まぁ面白おかしく配布できたので、ひとまず良かったと思っています。

裏トークでMCをやった

裏トークという企画、ここ数年のYAPCでは特にオンラインにおけるにぎやかしということでやってきたのですが、今年はオフライン、しかもYouTubeで我々の様子がうかがい知れる状態での敢行でした。

オンラインで自分らの様子が流れることについては特に気にならなかったんですが、オフラインイベントということもあって、様々な方が飛び入り参加するというスタイルが斬新かつ面白かったです。@dankogaiさんや@motemenさん、@kazeburoさん、@mackee_w(まこぴー)さん、@uzullaさん、@bonnuさん、@__papix__さん、@karupaneruraさん、その他多数の方に飛び入り参加していただきました。本当にありがとうございます。

1点、反省点というか何とかできないものか、と思う点として、我々MC陣がなかなかブースを離れられないため、もっとたくさんの人たちとコミュニケーションをとりたかったなーという思いがあります。

ちなみにわたしが個人的に好きだなーとなったトーク、本編では@mackee_w(まこぴー)さんの「デプロイ今昔物語 〜CGIからサーバーレスまで〜 」がとても良かったと思いますし、@moznionさんの「ソフトウェアエンジニアリングサバイバルガイド: 廃墟を直す、廃墟を出る、廃墟を壊す、あるいは廃墟に暮らす、廃墟に死す」は弊社若手エンジニアのお気に入りだったようでした。前日祭のRejectConでは@codehexさんの「Customer Experience Journey in NOT A HOTEL」がぶっ刺さりましたね。

学生さんと話した

弊社、なんと学生旅費支援プログラムのスポンサーもやっていました。そのため、学生さんとランチ休憩の時間帯にお話をする企画に参加しており、まぁなんというか、非常に雑な会社紹介LTをさせていただきました。

また、学生さんたちの質問に答えるビンゴ企画というものもあり、こちらではわたしの飾らない率直な回答をさせていただきました。

なかなか学生さんと語らう機会は少ないですので、こちらとしても学生さんが気にしている点等、大変勉強となりました。

前日祭LTバトルの査読をやった

うちの会社の@mini_bg_pro_N(あきた)さんが前日祭LTバトルで東側MVCを受賞したんですが、その査読をさせていただきました。

その中で、Perlに関する感想をが書いてあるスライドがあり、TIOVEのあのグラフを引用していたので、あきたさんには「ここを少しだけ説明したら飛ばすと笑いが取れそう」とアドバイス。

そうしたところ、あきたさん、適宜「飛ばし芸」を入れると面白いと悟ったのか、本番では彼自身の判断で時間が足りなくなる可能性も考慮し、Expressの解説も飛ばしてもうひと笑いを取るという職人技をやってのけたのでした。凄い。

世界一の笑顔を撮った

最高の朝食を食べた

さいごに

@ar_tama(あらたま)さん、ベストスピーカー賞おめでとうございます。わたしはトークを聴くことができなかったので、アーカイブが出たら絶対見るぞ!という気持ちで心待ちにしております。

そしてスタッフの皆さん、本当にお疲れさまでした&ありがとうございます。終わった後、スタッフの人たちともコミュニケーションをとる機会が得られたのはとても良かったです。

裏トークMCの仲間である@myfinderさんと@soudaiさん、お疲れさまでした。およそ12時間にわたる長丁場を我々は全部楽しんで過ごしましたね。それだけやったら、そりゃまぁ疲れると思いますので、風呂でも入ってしっかり体力回復しましょう。ついでに弊社のタオルも使ってやってくださいw

そして最後に、@__papix__さんと@azumakuniyukiさん、本当にお疲れさまでした。3年ぶりにオフライン開催のYAPCが復活できたのは、紛れもなくお二方の不屈の精神のおかげです。ありがとう。

DBのDumpファイルからスキーマクラスを作りたい

Rarmani - Dumpファイルからスキーマクラスを作ってくれるツール

わいとんです。まずはこちらの動画をご覧ください。

要するにこれはDumpファイルの中からCREATE TABLE文を抜き出して、それらを全て Moo basedなスキーマファイルにしてしまうというものです。

インストール方法

まだMetaCPANに上げるようなクオリティではないので、github止まりモジュールとなっています。

そのため、まずは git clone してくる必要があります。

$ git clone https://github.com/ytnobody/Rarmani.git

あとは cpanm 等をつかってインストールすればOK。

$ cd Rarmani

$ cpanm .

無事にインストールが完了すると rarmani コマンドが使えるようになります。

rarmaniコマンドの使い方とオプション

動画でも実際に rarmani コマンドの使い方を解説していましたが、改めて説明しておきますと、概ね以下のような使い方をします。

1
$ rarmani --driver=[ドライバー名] --namespace=[スキーマクラスの名前空間] < [ダンプファイル]

一応、全てのオプションについて解説しておきます。動画に登場しなかったオプションもあります。

  • driver : SQLの構文には細かい方言のようなものがあります。Rarmaniでは、DBMSごとのSQLの方言をdriverというもので吸収しております。現時点では MySQL, Pg, SQLite のいずれかを指定することができます。

  • namespace : 生成されるスキーマファイルの名前空間を指定する必要があります。動画内では MyApp::Schema としていましたが、ご自由に設定していただくとよろしいでしょう。

  • path : スキーマファイルをどのディレクトリに生成するかを指定できます。指定しない場合は現在のディレクトリを基準にスキーマファイルが出力されます。

  • roles : スキーマファイルはMooのクラスとなりますが、各スキーマクラスにて Moo::Role に準拠したロールクラスを複数指定することができます。例えば、DBIx::Mint::TableMoo::Role::ToJSON を指定する、等が可能です。

コードファーストとDBファースト

最近では「コードでスキーマを表現して、それをもとにDBにテーブルをつくる」というアプローチの「コードファースト」が流行しているようです。これ自体は開発速度の向上やDBのバージョニングの観点でメリットがあり、採用できるならしたほうが良いかな、というのが私の意見です。代表的なところですと、TypeScriptにおけるPrismaなどが該当するでしょう。

一方で、既存DBがある(しかも顧客情報満載である)場合やDBが軸となって設計されたシステム等、いつもコードファーストが最善とは言えないだろう、というのもまた私の意見です。その場合は旧来の「DBファースト」を選択せざるを得ません。

RarmaniはDBファーストのアプローチを、もう少し良い開発体験で進められるようにしたい、という思いから開発が始められました。Go言語では結構有名なORMapperであるSQLBoilerから少なからず影響をうけています。

さいごに

Rarmaniはまだまだテストコードが足りず、ドキュメンテーションが貧弱です。ドライバーも3種類しかありません。有志のご協力をいただけるととてもありがたいです。

そして、このエントリはPerl Advent Calendar 2022の10日目のエントリとなります。明日11日目はまだ埋まっていませんが、きっとどなたかPerlに愛のある方が書いてくださるのではないか、と期待しております。

ミニマルな開発会社を作りました

ミニマルな開発会社を作りました

※テクノロジーの話題ではありませんが、ご了承ください。

この度、私わいとんは 合同会社Y.pm(Y.pm LLC) というシステム開発会社を設立し、代表社員に就任いたしました。

きっかけは部門閉鎖

会社設立のきっかけは、それまでに勤務していた会社(以後「なんとか社」としておきます)において、部門閉鎖に伴う退職勧奨をうけたことでした。

退職勧奨についての詳細についてはここでは触れませんが、なんとか社は部門メンバー(全員エンジニア、私含め3名)に対し、誠意ある対応と手厚い処遇で退職オプションを設定してくれていました。

また、私個人としても40代の中だるみのようなものを感じておりました。具体的には、よりダイレクトに利益へ貢献する状況に身を置きたい、とひっそりと考えていたところでした。

そのようなことから、なんとか社の退職オプションはまさに「渡りに船」だったのです。

途中、なんとか社ではIoT関連のチャレンジや新規事業のシステム開発を行ったりなど、エンジニアとしてはなかなか新しいことをやらせていただいていたこともあり、とても感謝しています。

会社設立という選択をした理由

会社を辞めただけなら、再就職をするとか個人事業主としてやっていくなど、他の方法はいくらでもありました。

ただ、これらの方法を取らずに会社設立という方法を取ったのには、いくつか理由があります。

1. 案件の引き合いがあった。

もうこれはラッキーと言う他ないかもしれません。

むしろ案件の引き合いがあったからこそ、スムーズに会社設立の判断ができたのです。持つべきものは仲間だということが骨身に沁みました。この案件を紹介してくれた方には本当に感謝しています。

2. なんとか社の元同僚エンジニアたちと案件融通の見通しが立った。

実は元同僚エンジニアたちとは結構早い段階で「ひとまず個人事業主のギルドでも立ち上げよう」という話が出ていました。彼らとは今でも案件の融通や趣味の話、各自のスペシャリティ領域の情報共有などを日々行っています。

また、お互いの得意領域が見事にバラけていることも強みです。私たちが力を合わせればシステムがひとつでき上がります。特定分野だけの受注も可能で、誰が窓口になっても適切なメンバーに話が行くという体制ができ上がっています。

3. やってみたい開発組織構成案があった。

2とも一部被るのですが、受発注をベースとした最小構成の開発チームを作って動かしてみたいという思いがどこかにありまして、今回はその野望を実現するための下地をつくったに過ぎません。

受発注ベースの開発チームの亜種として、ニアショア開発のチームを動かし育てたいという野望もあります。

現時点ではあまり多くを語れませんが、恐らく近々にニアショア開発チームを組織して開発を進めることになるのではないか、と勝手に想像しています。

参考にしたサイト

大いに参考にさせていただいたのが以下のサイトです。

そのほか

  • 正社員採用はしていないんですか?

    • してません。こちらの必要に応じて発注をかける可能性はゼロではありませんので、貴方のスペシャリティを教えてください。
  • 御社のサイト、代表の写真がおかしいんですけど・・・

    • すみません、まともなセルフィーがなかったので、仮です。私のことをかっこよく素敵に撮影してくれる人がいたらいいのですが・・・

YAPC::Japan::Online 2022 の裏番組でMCを務めます!

YAPC::Japan::Online 2022 の裏トークチャンネルでMCを務めます!

Japan.pm 2021からYAPC::Japan::Online 2022へ

みなさんこんにちは。@ytnobodyです。

さて、昨年開催されたJapan.pm 2021というカンファレンスをご存知でしょうか。

Japan.pm 2021は、Perlの祭典YAPC::JapanのDNAを受け継いだ、オンラインカンファレンスでした。このイベントには日本全国のPerl愛好家やエンジニアたちが集い、情報交換を行う場も提供され、結果大変盛り上がりました。

また当時は私わいとん(@ytnobody)のほかに、まいんだー(@myfinder)氏とそーだい(@soudai1025)氏の3人がMCとして裏トークチャンネルを切り盛りしまして、こちらも大盛況となりました。

そんなJapan.pmですが、YAPC::Japan::Online 2022としてさらにスケールアップし、来る3/4(金)および3/5(土)に開催されます!

そしてそして!!!YAPC::Japan::Online 2022 でも裏トークチャンネルの開設が決定しており、私を含め昨年と同じ3名のMC(通称:大井町.pm)が場を盛り上げます!!!!!

さらに今年は、Perl入学式で大活躍中のお二方@xtetsuji氏と@sironekotoro氏による裏トークチャンネルも開設されるそうです!

詳しくはYAPC::Japan::Online 2022の運営ブログで!

近況報告

近況報告

公には書きづらい状況にあります。必要があれば対面で話す用意がありますので、ご希望の方はTwitterにてご連絡ください。

昨年アウトプットしなかった理由

基本的には「業務に差し支える可能性があるため」なのですが、実際のところはもう少し込み入っておりまして・・・お察しください。

今なにしてるの?

これを書いている時点ではまだ兼業フリーランスをやってますが、のっぴきならない事情により、4/12~は完全に無職となる予定です。それ以降でお仕事下さる方、いましたらお知らせください。

会社を起こすかもって話をしてた気がしますが?

高確率で会社(マイクロ法人)を起こすことになると思います。ただし、それは4/15以降の話になりそうです。また、その会社はITの会社ではないと思います。

IT辞めるんですか?

辞めません。まだ公には書けませんが、少なくとも食い扶持を稼ぐ必要がある以上、IT業界にはまだまだお世話になる所存です。

一つ前の項目で書いた通りマイクロ法人を起こす可能性が高いのですが、必要に応じてIT関連の法人も用意する可能性があります。

元気がない気がするんですけど・・・

めちゃくちゃ元気です!おかれている状況が複雑なだけであり、心身ともに元気ビンビンであります!

多忙なんですか?

暇です!つとめて時間的余裕がある生活を送ることを心がけております!

ところであんた誰?

詳しくはこちらをどうぞ。

コピペコード検出で定番のPMDはdockerから使うと楽

PMDをつかうとコピペコードの検出ができる

ある程度プロジェクトが進捗して運用フェーズに入る頃、コードの要所要所にコピペの跡が垣間見えた、なんて経験があるエンジニア諸氏は結構いるのではないでしょうか。

こういった重複コード(いわゆるコピペコード)を検出するのに便利なツールとして、比較的枯れたものの一つにPMDというものがあります。

このPMDはもともとJavaのソースコードについて、以下のような、リファクタリングに役立つ解析を行ってくれるものです。Javaには詳しくありませんが、どうやらJavaでは定番のツールかと思われます。

  • 予測可能なバグの検出(空のtry/catchなど)
  • 未使用のコード(Dead code)の検出
  • 最適ではないコードの検出(無駄なString/StringBufferの使用など)
  • 複雑すぎるコードの検出(不要なif, whichに置き換え可能なforなど)
  • 重複したコードの検出

そしてPMDは今ではプラグインを通じ、Javaの他に以下の言語に対応しています。

  • C++
  • C#
  • Fortran
  • Go
  • JavaScript
  • JSP
  • Matlab
  • Objective-C
  • PHP
  • PL/SQL
  • Python
  • Ruby
  • Velocity
  • XMLおよびXSL
  • Scala

残念ながらElixirには未対応のようです。またPerlにはpmd-perlというプラグインがありましたので、もしかすると対応しているのかもしれません。

Docker Imageを使うと楽

さて、このPMDですが、Docker Hubにいくつかビルド済みイメージがあります。私のお気に入りは rawdee/pmd:6.19.0-alpine です。

では早速、コピペコードの検出を試してみましょう。

今回解析の対象としたのは frankrap/bybit-api です。これは暗号通貨取引所「ByBit」のGo向けAPIライブラリです。

これを対象として選んだ理由は、手元に既にあり、ざっとコードを読んでそこそこ重複するコードがありそうだと思ったのと、一般公開済みのコードだからです。

あなたの環境がdockerを利用可能な状態であれば、プロジェクトルート直下で以下のコマンドを実行するだけで、コピペコードの検出が可能となります。

1
2
3
4
5
$ docker run --rm -v $(pwd):/src rawdee/pmd:6.19.0-alpine \
cpd --language go \
--files ./**/*.go \
--minimum-tokens 100 \
--exclude ./**/*_test.go

実際に bybit-api に対して実行した結果が以下の通りとなります。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
$ docker run --rm -v $(pwd):/src rawdee/pmd:6.19.0-alpine cpd --language go --files ./**/*.go --minimum-tokens 100 --exclude ./**/*_test.go
Found a 25 line (108 tokens) duplication in the following files:
Starting at line 197 of /src/./rest/api.go
Starting at line 251 of /src/./rest/api.go

log.Printf("PublicRequest: %v", fullURL)
}
var binBody = bytes.NewReader(make([]byte, 0))

// get a http request
var request *http.Request
request, err = http.NewRequest(method, fullURL, binBody)
if err != nil {
return
}

var response *http.Response
response, err = b.client.Do(request)
if err != nil {
return
}
defer response.Body.Close()

resp, err = ioutil.ReadAll(response.Body)
if err != nil {
return
}

if b.debugMode {
log.Printf("PublicRequest: %v", string(resp))

上記のように、コピペ箇所の検出ができていることがわかるかと思います。ここでは ./rest/api.go の197行目から25行ぶんのコードについて、251行目にも同一のコードが登場していることを表しています。

それにしても、この長いdockerコマンドを毎回入力するのは非常に面倒なことです。もしご自身のプロジェクトでコピペ検出をちょいちょいやっていきたいと思っても、こんなに長いコマンドを入力するだけでやる気が萎えてしまうというものです。

ですので、Makefileに以下のようなタスクを追加しましょう(go言語のプロジェクトの場合です)。

1
2
cpd:
docker run --rm -v $(pwd):/src rawdee/pmd:6.19.0-alpine cpd --language go --files ./**/*.go --minimum-tokens 100 --exclude ./**/*_test.go

このようにすることで make cpd とするだけでプロジェクト配下のコードについてコピペ検出が可能となります。

なお、上記のタスクはコピペコードが存在するとmakeに失敗しますので、このことを利用して各種CIに組み込むなどの継続的な措置を取ることも可能です。