CircleCIでcheckoutするならGit入りイメージを使おう

CircleCIのジョブが下記のエラーで失敗していた。

Either git or ssh (required by git to clone through SSH) is not installed in the image. Falling back to CircleCI's native git client but the behavior may be different from official git. If this is an issue, please use an image that has official git and ssh installed.

Enumerating objects: 208, done.
Counting objects: 100% (208/208), done.
Compressing objects: 100% (135/135), done.
Total 1015 (delta 130), reused 134 (delta 61), pack-reused 807

reference not found

git cloneで「reference not found」になっている。イメージにGitクライアントがインストールされておらず、CircleCIネイティブのGitクライアントにフォールバックしたものの、おそらくその実装に問題があるためエラーになったのだろう。

エラーメッセージにあるとおり、公式のGitが入ったイメージを使えば解決しそうだ。

これまでは下記のように「docker」イメージを指定していた。

    docker:
      - image: docker

これを、Git入りのイメージに変えたところ、ジョブが成功した。

    docker:
      - image: docker:git

今まで失敗しなかったのは、たまたま運がよかったのだろう。

開発用サーバもOracle Cloud Free Tierに乗り換えた

リリースチェッカーむびりすなどの開発用サーバを、さくらのVPSから、Oracle Cloud Free TierのVMインスタンスに移行しました。さくらのVPSに不満があったわけではなく、節約目的です。年間17,055円が浮くことになります。

リリースチェッカーの本番用サーバは昨秋に移行済みです。開発用サーバも早く移行したかったのですが、Oracle Cloudの空きがなかなか出ず、時間がかかってしまいました。やはり人気なのでしょう。


以下、Oracle Cloudとは無関係な話です。今回の移行を機に、むびりすのRailsを5.2.2から6.0.2.2に上げました。「rails app:update」して「config.hosts」を追加しただけで、あっさり動きました。ついでに、Rubyも2.6.0から2.7.1に上げています。

さらに無関係な話ですが、昨秋の記事に書いたリリースチェッカーの手動デプロイは、その後、DockerのSwarmモードに変更しました。「docker service update」コマンドひとつでデプロイできて便利です。


そんなふうに最近は過ごしています。映画館に行くのが趣味だったのですが、今は無理なので、あいた時間を自作サービスの改善にあてている感じです。

本業は以前からフルリモートで変わりありません。今は、ぼくにとっては初物尽くしのGo+GitHub Actions+ECS(Fargate)でマイクロサービスを作っています。仕事のあるうちは、やるべきことを淡々とやるのがよいと思っています。

Intent-ResponseパターンでFat Controllerをリファクタリングしてみる

Fat Controllerをコードで防ぐ方法はないか?」という記事で、まさにFat Controllerを防ぐ私案として、Intent-Responseパターンを提示した。分かりづらいと思うので、もう少し説明してみる。

Fat Controlerの実例

ぼくが思うFat Controlerは、こういうものだ。

  def diff
    journal = Journal::AggregatedJournal.containing_journal(@journal)
    field = params[:field].parameterize.underscore.to_sym

    unless valid_diff?
      return render_404
    end

    unless journal.details[field].is_a?(Array)
      return render_400 message: I18n.t(:error_journal_attribute_not_present, attribute: field)
    end

    from = journal.details[field][0]
    to = journal.details[field][1]

    @diff = Redmine::Helpers::Diff.new(to, from)
    @journable = journal.journable
    respond_to do |format|
      format.html
      format.js do
        render partial: 'diff', locals: { diff: @diff }
      end
    end
  end

OpenProjectというRails製のOSSに含まれているコードである。JournalsControllerのアクションだ。

それほどFatに見えないかもしれないが、ビジネスロジックもプレゼンテーションロジックも含まれていて、コントローラが責務を担いすぎているようにぼくには思える。

たとえば、@journable の扱いがおかしい。JS用のテンプレートでは @journable を参照しない。だから、下記のように、HTMLが要求されたときだけ設定されるべきだ。

    respond_to do |format|
      format.html do
        @journable = journal.journable
        render
      end
      format.js do
        render partial: 'diff', locals: { diff: @diff }
      end
    end

このような混乱が生じるのは、コントローラがプレゼンテーションロジックまで担ってしまっているからだ。

どうしたら防げるだろうか。

Intent

まず、ビジネスロジックについて考えてみよう。

JournalsController#diff のビジネスロジックは「diffの取得」である。HTMLが要求されたときでもJSが要求されたときでもレスポンスに含まれるのはdiffだけだ。

こうしたビジネスロジックを「Intent」と呼ぶことにする。JournalsController#diff にアクセスするユーザの意図、すなわちIntentは「diffの取得」ということである。

Response

一方、プレゼンテーションロジックについてはどうか。

JournalsController#diff のレスポンスは、表現形式(HTML or JS)を無視すれば、3種類考えられる。ステータスコードが404、400、200の場合に対応するものだ。

3種類のレスポンスに必要なロジックはそれぞれ異なる。404の場合は固定のレスポンスボディを返すだろうし、200の場合は表現形式に応じたレスポンスボディを組み立てることになるだろう。

こうしたレスポンスは、そのまま「Response」と呼ぶことにする。Viewと呼ばないのは、たとえば「204 No Content」のようにレスポンスボディのない応答もありえるからだ。Responseの呼ぶのがふさわしいだろう。

Fat Controlerのリファクタリング

ここまででビジネスロジックをIntentに、プレゼンテーションロジックをResponseに、それぞれ任せる方針が見えた。個々のIntentやResponseはオブジェクトとして扱うのが自然だろう。

となると、JournalsController#diff は下記のようにリファクタリングするのがよさそうだ。

  def diff
    status_code, outcome = Journal::Intent::Diff.new.execute(self)
    responses = {
      404: Journal::Response::FieldNotFound,
      400: Journal::Response::AttributeNotPresent,
      200: Journal::Response::Diff
    }
    responses[status_code].new.build(self, status_code, outcome)
  end

こうすれば、コントローラはIntentの実行と、実行結果に応じたResponseの組み立てを制御するだけでよくなる。outcomeはIntentの結果を意味し、200時にはdiffが、400時にはエラーメッセージが含まれる。

(IntentやResponseにコントローラ自身を渡しているのは、渡した先でparamsやrenderなどを呼び出すと想定したからだ。他に良い手があるかもしれない)

Intent-Responseパターンのメリット

Intent-Responseパターンが絶対の正解だとは言わない。が、コントローラの可読性の低さに起因するエンバグは減るだろう。それだけでも、ぼくにはメリットが大きい。

プロフィール
Web開発者。現在の関心事はシステム品質の改善(特に性能効率性と保守性)。JAPAN MENSA会員。
カテゴリ別アーカイブ
記事検索