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

最近の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に組み込むなどの継続的な措置を取ることも可能です。

【Ubuntu Linux】 自宅サーバで健康維持

テレワーク時代の健康維持

私のようなITエンジニアの強みとして「どこでも仕事ができる」というものがあります。これは他の業種の人からすると、非常に大きなメリットなのですが、一方で気をつけなくてはいけなくなるのが「健康」です。ITエンジニアは長時間の座り仕事になりますし、しかもパソコンの画面を注視するものですから、どう考えても健康に良いわけがありません。

私のように中年も半ばに差し掛かると、どうしても皮下脂肪も付きやすくなりますし、筋肉量もどんどん目減りしていきます。さらに身体がしなやかさを失いますから、腰痛や偏頭痛、慢性的な疲労感に苛まれていくことになります。

そこで、ITエンジニアらしい方法を使い、毎日決まった時間に「アレ」をすることにしました。

国民の健康維持の為に開発された「アレ」を自宅サーバで

さて、「アレ」についてそろそろ説明しておきます。「アレ」とは国民の体力向上と健康の保持や増進を目的とした体操、つまり「ラジオ体操」です。

このラジオ体操を、自宅サーバとして稼働しているASUS EeePC 1015PEMカスタム(SSD化&RAM増設→4GB / OS: lubuntu-18.04 LTS [bionic])で毎日11:00に再生することで、強制的にラジオ体操の時間を作ってしまおうという目論見です。

Ubuntu Linuxをコマンドラインで操作できる程度の知識と、余ったノートPCがあればできる内容となっていますので、ぜひ健康維持の参考にしてみてください。

必要なパッケージのインストール

強制ラジオ体操タイムに必要なパッケージは以下のものとなります。すべてapt installで導入可能です。

  • mplayer
  • ffmpeg
  • youtube-dl
  • alsa-base
  • alsa-utils

導入のためのコマンドは以下の通りです。

1
2
3
4
5
6
7
$ sudo apt update &&
sudo apt install \
mplayer \
ffmpeg \
youtube-dl \
alsa-base \
alsa-utils

YouTubeからラジオ体操の動画をDLする

導入したyoutube-dlコマンドを使い、ラジオ体操の動画を自宅サーバにダウンロードします。

1
$ youtube-dl "$MOVIE_URL"

上記のコマンドを実行すると、動画タイトルと同じ名前のwebm形式のファイルが出来上がっていると思います。($MOVIE_URLは任意のYouTube動画のURLに置き換えてください。)

ただ、このままのファイル名ではかなり取り扱いが面倒ですから、以下のような感じで半角英数のファイル名に変更しましょう。

1
$ mv 'ラジオ体操 第1 ・第2、首の体操-jXXXXXXXXXX.webm' Radio-Taisou.webm

これで動画ファイルRadio-Taisou.webmが出来上がりました。

動画をmp3に変換する

実際に動画のまま取り扱っても問題はないのですが、動画のままではどうしても若干面倒ごとが増えてしまいます。しかも今回は映像部分は不要で、音声だけが欲しいので、動画をmp3に変換します。

動画からmp3音声を抜き出すのにffmpegを使います。以下のような感じで変換可能です。

1
2
3
4
5
6
$ ffmpeg \
-i Radio-Taisou.webm \
-vn \
-acodec mp3 \
-af "volume=1.3" \
Radio-Taisou-audio.mp3

上記コマンドの各オプションはそれぞれ以下のような意味となります。

  • -i Radio-Taisou.webm 変換元となるファイルを指定
  • -vn 映像(ビデオ)オフ
  • -acodec mp3 出力音声の形式をmp3にする
  • -af "volume=1.3" 出力音声のボリュームを1.3倍にする
  • Radio-Taisou-audio.mp3 出力先となるファイルを指定

このようにすると、Radio-Taisou-audio.mp3が作成されます。

再生デバイス(スピーカーなど)を確認する

次に、どの再生デバイスから音を出すのかを確認します。コマンドは以下の通りとなります(sudo権限が必要)。

1
$ sudo aplay --list-devices

私の環境では、出力先に利用できるデバイスが一つ(EeePCに標準付属のスピーカー)しかありませんでした。

1
2
3
4
5
6
7
ytnobody@lubuntu1804:~$ sudo aplay --list-devices
[sudo] ytnobody のパスワード:
**** ハードウェアデバイス PLAYBACK のリスト ****
Home directory not accessible: 許可がありません
カード 0: Intel [HDA Intel], デバイス 0: ALC269VB Analog [ALC269VB Analog]
サブデバイス: 1/1
サブデバイス #0: subdevice #0

ここで重要となるのが、カード 0デバイス 0のところです。このあと音声を再生するときにこの番号を使いますから、しっかりと覚えておきましょう。

再生してみる

それでは実際に再生してみましょう。コマンドラインからmp3を再生するにはmplayerを使います。コマンドは以下の通りです。

1
2
3
$ sudo mplayer \
-ao alsa:device=plughw=0.0 \
Radio-Taisou-audio.mp3

このコマンドでは、-ao alsa:device=plughw=0.0 の箇所で再生デバイスを指定しています。例えば再生デバイスがカード 0デバイス 1の場合はplughw=0.0の代わりにplughw=0.1とする必要があります。ご自身の環境に合わせて変更してください。

うまく再生できた場合、スピーカーなどからラジオ体操のピアノ伴奏が聞き取れるはずです。音声の再生を終了する場合はCrtl-CかEscでmplayerを終了させます。

定期実行させる

再生までできたら、あとは任意の時間に自動再生されるよう、cronで定期実行させるだけです。

私の場合、昔からの手癖で直接/etc/crontabに定期実行の設定を書いちゃったのですが、その内容は以下の通りです。

1
00 11 * * * root mplayer -ao alsa:device=plughw=0.0 /home/ytnobody/Radio-Taisou-audio.mp3 > /dev/null 2>&1

最後に&gt; /dev/null 2&gt;&amp;1を追加してありますが、これはcronジョブが実行される際に、mplayerのSTDOUTとSTDERRが邪魔だったので、両方とも/dev/nullに捨てるようにしたのです。

まとめ

テレワーク時代の健康維持の為に、自宅サーバでラジオ体操を毎朝11時に流すようにしました。

座り仕事が続き、腰痛などに悩まされることが多くなった人はぜひラジオ体操タイムを作って、健康維持に努めましょう。

Generating Kotlin code from Protocol Buffers (without JVN on your host)

Gradle? JVM? Looks as chainsaw in this case.

Protocol Buffers is famous IDL(Interface Description Language) in these days. And the uber/prototool is needful tool for generating interface codes in any languages.

However information about generating kotlin code is quiet few in today.

Basically I was knowledgeless about kotlin before generating kotlin codes. And the our project is written in golang.

Okay, then I found an entry that telling as “JVM and gradle are needed when generating kotlin code through protoc”.

Hmm, “place build.gradle will be in our project”? Sounds not so good. Because our project is written in golang. Don’t forget, I just want “generating kotlin codes from protocol buffers“. This method looks as cutting sashimi with chainsaw.

Conclusion: Docker solves as like sashimi knife

In this case, requirement to environment for solving the issue is so huge. “Huge requirement to environment… I’ve got it!” yes, it is the Docker Hub. So I found the container image that fits this case.

It is the moia/prototool-docker (codes on GitHub).

Usage is followings.

1
2
3
4
5
6
7
8
9
10
11
12
### prototool.dockerfile
FROM moia/prototool-docker

RUN mkdir /proj
WORKDIR /proj

CMD protoc --kotlin_out=./proto/kotlin ./proto/definition/*.proto

### In your terminal
$ docker build -f ./prototool.dockerfile -t prototool .
$ docker run --rm -v /path/to/myproj:/proj -t prototool

Finally I got succeed to generate kotlin code from protocol buffers, with moia/prototool-docker that is instead of JVM on my host.

plenvsetup v0.06 has been released

Let’s setup your own perl environment without system-perl

Today I released plenvsetup v0.06. plenvsetup is a quick setup tool for perl environment with plenv.

In the latest version, plenvsetup uses Shoichi Kaji(skaji)‘s perl-install instead of Perl-Build. This allows, we can install perl without system-perl under plenv.

Now we can use new plenvsetup with following one-liner.

1
$ curl -sL https://is.gd/plenvsetup | bash  

You may zsh instead of bash.

Build on a Docker container

plenvsetup works on a Docker container it based on latest alpine linux image too.

For example, following Dockerfile setups the perl-5.30.0 with plenvsetup on container.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
FROM alpine  
WORKDIR /root

### install required dependencies (perl not required!)
RUN apk add bash libc-dev gcc make patch git curl

### download plenvsetup
RUN curl -sL https://is.gd/plenvsetup > plenvsetup && chmod +x plenvsetup

### use plenvsetup on bash
ENV SHELL=/bin/bash
RUN bash plenvsetup

### install perl-5.30.0 with plenv
RUN .plenv/bin/plenv install 5.30.0
RUN .plenv/bin/plenv global 5.30.0
RUN .plenv/bin/plenv rehash

CMD /bin/bash

Then, you can build the Dockerfile, and run perl-5.30.0 within the container.

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
$ docker build -t ytnobody/plenvsetup .  
Sending build context to Docker daemon 2.048kB
Step 1/10 : FROM alpine
---> cc0abc535e36
Step 2/10 : WORKDIR /root
---> Using cache
---> 5a63e9728352
Step 3/10 : RUN apk add bash libc-dev gcc make patch git curl
---> Using cache
---> 0fed43f11239
Step 4/10 : RUN curl -sL https://is.gd/plenvsetup > plenvsetup && chmod +x plenvsetup
---> Using cache
---> 2b8ce1c5f1fa
Step 5/10 : ENV SHELL=/bin/bash
---> Using cache
---> 6f661e8cf191
Step 6/10 : RUN bash plenvsetup
---> Using cache
---> 24f2c6c713f0
Step 7/10 : RUN .plenv/bin/plenv install 5.30.0
---> Running in 86f08ede1c38
---> Downloading https://cpan.metacpan.org/authors/id/X/XS/XSAWYERX/perl-5.30.0.tar.gz
---> Unpacking /root/.plenv/cache/perl-5.30.0.tar.gz
---> Applying Devel::PatchPerl 1.80 (patchperl-extracted 0.0.1)
---> Building perl 5.30.0
---> See /root/.plenv/build/1578556140.1/build.log for progress
---> ./Configure -des -Dprefix=/root/.plenv/versions/5.30.0 -Dscriptdir=/root/.plenv/versions/5.30.0/bin
---> make
---> make install
---> Successfully installed perl 5.30.0
Removing intermediate container 86f08ede1c38
---> 73c2b52fa025
Step 8/10 : RUN .plenv/bin/plenv global 5.30.0
---> Running in 439f41b196dd
Removing intermediate container 439f41b196dd
---> 8e819e1afd8c
Step 9/10 : RUN .plenv/bin/plenv rehash
---> Running in 31d3278595ce
Removing intermediate container 31d3278595ce
---> 28e28a126931
Step 10/10 : CMD /bin/bash
---> Running in c67a1e299db0
Removing intermediate container c67a1e299db0
---> 3ceac81cf2f4
Successfully built 3ceac81cf2f4
Successfully tagged ytnobody/plenvsetup:latest

$ docker run --rm -it ytnobody/plenvsetup
bash-5.0# perl -v
This is perl 5, version 30, subversion 0 (v5.30.0) built for x86\_64-linux
(with 1 registered patch, see perl -V for more detail)
Copyright 1987-2019, Larry Wall
Perl may be copied only under the terms of either the Artistic License or the
GNU General Public License, which may be found in the Perl 5 source kit.
Complete documentation for Perl, including FAQ lists, should be found on
this system using "man perl" or "perldoc perl". If you have access to the
Internet, point your browser at http://www.perl.org/, the Perl Home Page.

So easy.