宝石開発者ガイドライン

GitLabは、モノリシックなコードベースにおけるコードの再利用性とモジュール性を向上させるツールとしてGemsを使用しています。

私たちは、その機能が高度に分離されており、私たち自身で他のアプリケーションで使用したい場合、またはそれがより広いコミュニティに利益をもたらすと考えられる場合に、コードベースからライブラリを抽出します。

gemにコードを抽出することで、gemが私たちのアプリケーションコードに隠れた依存関係を含まないようにすることもできます。

Gemは、GitLabのビジネスロジックから切り離され、別々に開発することができる、分離された機能を実装する場合に常に使用する必要があります。

Railsのコードベースで新しいgemsを抽出するのに最適な場所はlib/フォルダです。

私たちのlib/フォルダには、汎用的なコード、GitLab 固有のコード、他のコードベースと密接にインテグレーションされたコードが混在しています。

コードベースの一部を Gem として抽出するかどうかを決めるには、次のような質問を自分に投げかけてみましょう:

  1. このコードは、独立した小さなプロジェクトとして実行できる汎用的なものですか?
  2. Monolithの外部で内部使用されることを期待していますか?
  3. 別のコンポーネントとしてリリースすることを検討すべき、より広いコミュニティにとって有用なものでしょうか?

上記の質問のいずれかに答えがYesの場合、新しいGemを作成することを強く検討すべきです。

同じリポジトリに新しいGemを作成することから始めて、後でそれを別のリポジトリにマイグレーションするかどうかを評価することができます。

caution
悪意のある行為者が抽出したGemの名前を詐称するのを防ぐために、Gem名を予約する手順に従ってください。

ジェムを使用する利点

Gemsを使用することで、コードのメンテナンスにいくつかの利点があります:

  • コードの再利用性 - Gemsは、単一の目的を果たす分離されたライブラリです。Gemsを使用する場合、共通の関数をシンプルなパッケージに分離することができ、十分に文書化され、テストされ、異なるアプリケーションで再利用されます。

  • モジュール性 - Gemsは、自己完結型ライブラリ内に特定の機能をカプセル化することで、分離を作成するのに役立ちます。これは、コードをよりよく整理し、特定のモジュールのオーナーをよりよく定義し、特定のgemsのメンテナーやアップデートを容易にします。

  • 小規模 - 分離された一連の機能を実装するため、設計上gemは小規模になります。小さなプロジェクトは、理解、拡張、メンテナーがより簡単です。

  • テスト - Gemは小さいので、Gemを使用すると、すべてのテストを実行するのが非常に速くなります。gemはパッケージ化されており、あまり頻繁に変更されないので、CIテスト時間を改善するためにテストを実行する頻度を減らすこともできます。

gemのネーミング

宝石は3つの異なるケースに分類されます:

  • unique_gem:gem に GitLab 固有のものが含まれていない場合は、gem 名にgitlab を含めないでください。
  • existing_gem-gitlab:公開されているgemをフォークして修正・拡張する場合は、Rubygemsの規約に従って-gitlab
  • gitlab-unique_gem:GitLabプロジェクトのコンテキストでのみ有用なgemには、gitlab- 接頭辞を付けましょう。

既存のgemの例:

  • y-rb:yrsのRubyバインディング。Yrsの “wires “はYjsフレームワークのRust移植版。
  • activerecord-gitlab:activerecord 公開 gem に GitLab 固有のパッチを追加。
  • gitlab-rspec andgitlab-utils: 特定のコンテキストで役立つGitLab固有のクラスセット、またはコードの再利用。

同じリポジトリの

既存のコードベースからGemsを抽出する場合、GitLab monorepoのgems/

そうすることで、gemsの利点(モジュール化されたコード、開発者のテスト実行の迅速化)を得ることができ、複雑さ(レポ間の変更、新しい権限、複数のプロジェクトなどの調整)を防ぐことができます。

同じリポジトリに保存されたgemsは、path: 構文でGemfile

caution
悪意のある行為者が抽出したGemの名前を詐称するのを防ぐために、Gem名を予約する手順に従ってください。

新しいGemの作成と使用

新しいGemを追加する例を見ることができます:121676.

  1. Gemの命名規則に従って、gemの良い名前を選んでください。
  2. gems/<name-of-gem>bundle gem gems/<name-of-gem> --no-exe --no-coc --no-ext --no-mit を使って新しいgemを作成します。
  3. gems/<name-of-gem>.git フォルダーをrm -rf gems/<name-of-gem>/.gitで削除します。
  4. gems/<name-of-gem>/README.md を編集して、Gemの簡単な説明を記述してください。
  5. gems/<name-of-gem>/<name-of-gem>.gemspec を編集し、次の例のようにジェムの詳細を記入します:

    # frozen_string_literal: true
       
    require_relative "lib/name/of/gem/version"
       
    Gem::Specification.new do |spec|
      spec.name = "<name-of-gem>"
      spec.version = Name::Of::Gem::Version::VERSION
      spec.authors = ["group::tenant-scale"]
      spec.email = ["engineering@gitlab.com"]
       
      spec.summary = "Gem summary"
      spec.description = "A more descriptive text about what the gem is doing."
      spec.homepage = "https://gitlab.com/gitlab-org/gitlab/-/tree/master/gems/<name-of-gem>"
      spec.license = "MIT"
      spec.required_ruby_version = ">= 3.0"
      spec.metadata["rubygems_mfa_required"] = "true"
       
      spec.files = Dir['lib/**/*.rb']
      spec.require_paths = ["lib"]
    end
    
  6. gems/<name-of-gem>/.rubocop.yml を更新します:

    inherit_from:
      - ../config/rubocop.yml
    
  7. 新しく追加されたGem用にCIを設定します:

    • gems/<name-of-gem>/.gitlab-ci.yml を追加します:

       include:
         - local: gems/gem.gitlab-ci.yml
           inputs:
             gem_name: "<name-of-gem>"
      
    • .gitlab/ci/gitlab-gems.gitlab-ci.yml に追加:

       include:
         - local: .gitlab/ci/templates/gem.gitlab-ci.yml
           inputs:
             gem_name: "<name-of-gem>"
      
  8. Gemfile の Gem を参照してください:

    gem '<name-of-gem>', path: 'gems/<name-of-gem>'
    

Gemの抽出例

gitlab-utils は、strong_memoizeGitlab::Utils.to_booleanのような、GitLab 開発者が使う一般的な組み込み関数を実装したクラスのセットを含む Gem です。

gitlab-database-schema-migrations は、データベースのマイグレーションをリポジトリに保存する方法を改善するRailsフレームワークの拡張を含むGemです。これはRailsの上に構築されるもので、GitLabというアプリケーションに特化したものではなく、他のプロジェクトに一般的に使われたり、アップストリームされたりする可能性があります。

先ほどと似たgitlab-database-load-balancing は、GitLab 固有のロードバランシングを Rails のデータベース処理に実装するための Gem です。これはかなり複雑で非常に特殊なコードなので、分離され、よくテストされたGemでその複雑さをメンテナーすることは、大きなモノリシックなコードベースからこの複雑さを取り除くのに役立つでしょう。

gitlab-flipper は、機能フラグをコードベースでサポートするためのカスタム拡張機能をすべて実装した、もうひとつの可能性のあるGemです。一貫性チェックや追加された機能フラグのオーナーを追跡するためのさまざまなヘルパーが追加されました。これはGitLabのビジネスロジックの一部ではないので、Flipper の実装をより良く追跡するために使うことができます。

activerecord-gitlab は GitLab 固有の Active Record パッチを追加する gem です。複雑さを分離するために、このようなパッチを個別に管理することはとても望ましいことです。

その他の使用例

gitlab-ci-config は、.gitlab-ci.yml を解析するために使われる私たちのすべての CI コードを含む潜在的な Gem です。このコードは、適切な抽象化がされていないため、現在GitLabアプリケーションと軽く連動しています。しかし、これを専用のGemに移行することで、アプリケーションのGitLabとのインテグレーションを処理するための様々なアダプタを構築できるようになります。例えば、includes:を解決するアダプタを定義するようなインターフェイスです。gitlab-ci-config Gemができれば、GitLab内部でもGitLab RailsやGitLab CLIの外部でも使えるようになります。

外部リポジトリの

一般的に、このようなことを行うには重大なデメリットがあるため、よく考えてから行うようにしたいものです。

外部リポジトリに保存されたGemsは、version の構文でGemfile を参照しなければなりません。それらは常にRubyGemsに公開されなければなりません。

使用例

GitLabでは多くの外部gemを使用しています:

デメリット

  • Gemsは(GitLabによってメンテナーされているものであっても)必ずしもメインのRailsアプリケーションと同じコードレビュープロセスを経るわけではありません。これはアプリケーションセキュリティにとって特に重要です。
  • 一貫したコードレビュー基準をサポートするDangerのようなツールを含め、CI/CDをゼロからセットアップする必要があります。
  • コードを別のプロジェクトに抽出するということは、機能を変更するために最低でも2つのマージリクエストが必要だということです。
  • 2回目のMRを必要とするgitlab-rails とのインテグレーションは、インテグレーションの問題が発見されるのが遅れる可能性があることを意味します。
  • gitlab-rails、レビュアーやメンテナーの人数が少ないため、コードのレビューに時間がかかり、”バスファクター “の影響が大きくなる可能性があります。
  • 新しいgemバージョンのリリース方法に関する一貫性のないワークフロー。現在のところ、どのように動作するかはライブラリのメンテナーの裁量に任されています。
  • gitlab-rails に比べてコードの可視性や露出度が低いため、ナレッジサイロを昇格させます。
  • GitLab には、レビュアーからメンテナーに昇格させるための明確なプロセスがあります。これは、抽出されたライブラリには当てはまらず、コードレビューのハードルを下げ、変更を出荷するリスクを高めます。
  • 私たち自身のgemの使い方のニーズと、より広いコミュニティのニーズが一致しないかもしれません。一般的に、もし私たちが自分たちのgemの最新バージョンを使っていないのであれば、それは警告のサインかもしれません。

潜在的な利点

  • より小さなリポジトリに対してCI/CDが実行されるため、フィードバックループがより速くなります。
  • プロジェクトをより広いコミュニティに公開し、外部からの貢献から利益を得ることができます。
  • リポジトリのオーナーは、変更をレビューする最適な対象者である可能性が高いため、gitlab-railsで適切なレビュアー探しの必要性を減らすことができます。

Ruby gemの作成と公開

新しいGemのプロジェクトは、常にgitlab-org/ruby/gems 名前空間 に作成する必要があります:

  1. gemの適切な名前を決めます。GitLab所有のGemであれば、Gem名の前にgitlab- をつけます。例えば、gitlab-sidekiq-fetcher
  2. 必要に応じて、ローカルで gem を作成したりフォークしたりします。
  3. 空の0.0.1 バージョンの gem を rubygems.org に公開し、gem 名が予約されていることを確認します。
  4. gitlab_rubygemsgitlab-qa のユーザーを新しいgemのオーナーとして追加します:

    gem owner <gem-name> --add gitlab_rubygems
    gem owner <gem-name> --add gitlab-qa
    
  5. オプション。以下のユーザーの一部またはすべてを共同オーナーとして追加します:
  6. オプション。その他関連する開発者を共同オーナーとして追加してください。
  7. https://rubygems.org/gems/<gem-name> にアクセスし、gem が正常に公開され、gitlab_rubygemsgitlab-qa もオーナーであることを確認します。
  8. gitlab-org/ruby/gems グループ にプロジェクトを作成します。このプロジェクトを作成します:
    1. 新規プロジェクトの手順に従ってください。
    2. CI/CD設定の手順に従ってください。
    3. 共有されたCI/CD設定を使用して、新しいgemバージョンをリリースして公開するには、そのgemの.gitlab-ci.yml

      include:
        - project: 'gitlab-org/quality/pipeline-common'
          file: '/ci/gem-release.yml'
      

      このジョブは gem のビルドと公開 (gitlab-org/ruby/gems グループから継承したgilab-qa Rubygems.org API トークンを使用して gem パッケージを公開します)、タグの作成、リリース、Generate changelog dataAPI エンドポイントを使用したリリースノートの作成を行います。

      いつ、どのように変更ログエントリファイルを生成するかについては、専用のChangelog entriesページを参照してください。GitLab プロジェクトとの一貫性を保つために、Gem プロジェクトでもgitlab-styles gem](https://gitlab.com/gitlab-org/ruby/gems/gitlab-styles/-/blob/master/.gitlab/changelog_config.yml)の[と同じ内容の changelog YAML 設定ファイルを.gitlab/changelog_config.yml に定義することができます。

    4. リリースプロセスを簡単にするために、gitlab-styles gem](https://gitlab.com/gitlab-org/ruby/gems/gitlab-styles/-/raw/master/.gitlab/merge_request_templates/Release.md) の[と同じ内容の.gitlab/merge_request_templates/Release.md MR テンプレートを作成することもできます (gitlab-styles を実際の gem 名に置き換えてください)。
    5. プロジェクトを公開する手順に従ってください。

注意事項場合によっては、gem を独自の名前空間に移動させたいことがあります。その例としては、当然ながら複数のプロジェクトを持つことになる場合(たとえば、プラグインを別のライブラリとして持っているような場合)や、GitLabのチームメンバーだけでなくGitLab外のユーザーがこのプロジェクトのメンテナーになることを想定している場合などがあります。後者の場合 (GitLab 外のメンテナー) は、現在 GitLab で働いている人が GitLab での仕事を超えてその gem をメンテナンスしたい場合にも当てはまります。

そのvendor/gems/

vendor/ の目的は、GitLab monorepo に外部の依存関係を取り込むことです。外部のリポジトリがありますが、シンプルにするために monorepo に保存します:

  • スクリプトや手動で外部リポジトリからプルする場合のみ、vendor/gems/ を使用しなければなりません。
  • vendor/gems/ は内部 gems の保存に使用してはいけません。
  • vendor/gems/ は GitLab monorepo でビルドできるように修正してもかまいません。
  • gems/ は GitLab monorepo の一部であるすべての内部 gems を保存するために使用しなければなりません(MUST)。
  • RubyGemsは、GitLab monorepo のgems/ にない、外部で保存されたすべての依存関係に使用しなければなりません(MUST)。

既存の gems をvendor/gems

  • 外部リポジトリがなく、現在vendor/gems/ に保存されている内部ジェムの場合:

    • 他のリポジトリで使用されているGemsの場合:

      • 独自のリポジトリにマイグレーションします。
      • RubyGemsでの公開を開始または継続します。
      • これらのGemsはGemfile のバージョンで参照され、RubyGemsから取得されます。
    • monorepoでのみ使用されるGemsの場合:

      • RubyGemsへの新バージョンの公開を停止します。
      • RubyGemsに依存しているアプリケーションがあるかもしれないので、すでに公開されているバージョンからの引き抜きは行いません。
      • これらのgemsはgems/ に移動します。
      • これらの宝石はGemfilepath: を介して参照されます。
  • vendor/gems/ 、モノレポの内部で販売されています:

    • アップストリームできない、あるいはまだアップストリームされていない修正が必要な場合は、リポジトリでメンテナーします。
    • サードパーティによってベンダリングされたgemが公開される可能性があります。
    • それらのジェムは弊社からRubyGemsに公開されることはありません。
    • RubyGemsに依存することはできないため、それらのGemはGemfilepath: を介して参照されます。

ジェム名の予約

私たちは、新しいgemを含むコードを公開する前に、RubyGemsで名前を横取りされないようにするための予防措置として、gem名を予約しています。

gem名を予約するには、Create and publish a Ruby gemの手順に従ってください:

  • バージョンは0.0.0
  • raise "Reserved for GitLab"という内容のファイルlib/NAME.rb を1つ含めます。
  • buildpublishを実行し、https://rubygems.org/gems/ をチェックして成功したことを確認します。