Gems 개발 가이드라인

GitLab은 모놀리식 코드베이스에서 코드 재사용성과 모듈화를 향상시키기 위한 도구로 Gems를 사용합니다.

기능이 매우 격리되어 있고 우리가 그것들을 다른 애플리케이션에서 사용하고 싶거나 더 넓은 커뮤니티에 이로움이 될 것으로 생각할 때 우리는 코드베이스에서 라이브러리를 추출합니다.

젬으로 코드를 추출함으로써 또한, 해당 젬에는 우리의 응용프로그램 코드에 대한 숨겨진 의존성이 포함되지 않도록 보장합니다.

젬은 항상 GitLab의 비즈니스 로직과 분리되어 고려될 수 있는 기능을 구현할 때 사용해야 합니다.

새로운 젬을 추출할 기회가 가장 많은 곳은 lib/ 폴더입니다.

우리의 lib/ 폴더에는 일반적이고 보편적인 코드, GitLab 특정 코드, 그리고 코드베이스의 나머지 부분과 긴밀하게 통합된 코드가 혼합되어 있습니다.

코드베이스 일부를 젬으로 추출할지 여부를 결정하기 위해 다음 질문을 스스로에게 할 필요가 있습니다:

  1. 이 코드는 별도의 작은 프로젝트로 수행될 수 있는 일반적이거나 보편적인가요?
  2. 이것이 모놀리스 외부에서 내부적으로 사용되리라고 예상하나요?
  3. 우리가 별도의 컴포넌트로 출시할만한 넓은 커뮤니티에 유용한가요?

위의 어느 질문에 대해 라는 대답이라면 새로운 젬을 만드는 것을 곰곰히 고려해야 합니다.

당신은 항상 동일한 리포지터리에서 새로운 젬을 생성하여 시작하고, 그것이 더 넓은 커뮤니티에서 사용될 때 별도의 리포지터리로 이주할지 여부를 나중에 평가할 수 있습니다.

caution
악의적인 사용자가 추출된 젬의 이름을 미리 등록하지 않도록 하려면 젬 이름을 예약해야 합니다.

젬 사용의 장점

젬을 사용하는 것은 코드 유지보수에 여러 가지 이점을 제공할 수 있습니다:

  • 코드 재사용 - 젬은 단일 목적을 제공하는 격리된 라이브러리입니다. 젬을 사용하면 일반 기능을 단순한 패키지에 격리시켜서 다른 응용프로그램에서 재사용 및 문서화하여 더 잘 사용할 수 있습니다.

  • 모듈화 - 젬은 특정 기능을 캡슐화하여 격리함으로써 모듈화를 돕습니다. 이는 코드를 더 잘 구성하는 데 도움을 주고, 주어진 모듈의 소유자를 더 잘 정의하며, 특정 젬을 더 쉽게 유지하거나 업데이트할 수 있도록 합니다.

  • 작음 - 격리된 일련의 기능을 구현하는 디자인으로 인해 젬은 소형입니다. 소형 프로젝트는 이해하기, 확장하기 및 유지하기가 훨씬 쉽습니다.

  • 테스트 - 젬을 사용하면 그것이 작기 때문에 모든 테스트를 빠르게 실행하거나 젬의 테스트를 매우 철저하게 진행하는 것이 훨씬 빨라집니다. 젬이 패키지화되어 자주 변경되지 않기 때문에 이러한 테스트를 덜 자주 실행하여 CI 테스트 시간을 단축할 수도 있습니다.

젬 이름 지정

젬은 세 가지 다른 케이스로 나뉠 수 있습니다:

  • unique_gem: 젬에 GitLab과 관련된 특정 내용이 없다면 젬 이름에 gitlab을 포함하지 마세요.
  • existing_gem-gitlab: 공개로 제공되는 젬을 복사하고 수정/확장하는 경우, Rubygems의 규칙에 따라 -gitlab 접미어를 추가하세요.
  • gitlab-unique_gem: GitLab 프로젝트 문맥에서만 유용한 젬에는 gitlab- 접두어를 포함하세요.

기존 젬의 예시:

  • y-rb: Yrs를 위한 루비 바인딩. Yrs “wires”는 Yjs 프레임워크의 Rust 포트입니다.
  • activerecord-gitlab: activerecord 공개 젬에 GitLab 특정 패치를 추가합니다.
  • gitlab-rspecgitlab-utils: 특정 컨텍스트에서 도움을 주거나 코드를 재사용하기 위한 GitLab 특정 클래스 집합.

동일한 리포지터리 내

기존 코드베이스에서 젬을 추출할 때, GitLab 모노레포의 gems/에 넣으세요

이를 통해 우리는 젬의 장점을 얻을 수 있습니다(모듈식 코드, 개발 환경에서 더 빠른 테스트 실행), 그리고 복잡성(리포지터리 간 변경 조정, 새 권한, 여러 프로젝트 등)을 방지할 수 있습니다.

동일한 리포지터리에 저장된 젬은 Gemfile에서 path: 구문으로 참조되어야 합니다.

caution
악의적인 사용자가 추출된 젬의 이름을 미리 등록하지 않도록 하려면 젬 이름을 예약해야 합니다.

새로운 젬 생성 및 사용

새로운 젬을 추가하는 예시를 확인할 수 있습니다: !121676.

  1. 젬 이름 지정 규칙을 따라 젬을 위한 좋은 이름을 선택하세요.
  2. bundle gem gems/<젬이름>으로 새로운 젬을 만드세요 (–no-exe –no-coc –no-ext –no-mit).
  3. <name-of-gem>에서 .git 폴더를 제거하세요: rm -rf gems/<name-of-gem>/.git.
  4. gems/<name-of-gem>/README.md를 수정하여 젬에 대한 간단한 설명을 제공하세요.
  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 = "젬 요약"
      spec.description = "젬이 하는 것에 대한 보다 구체적인 설명."
      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. 새로 추가된 젬을 위해 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 '<name-of-gem>', path: 'gems/<name-of-gem>'
    

젬의 의존성 지정

젬은 자체 Gemfile을 가지고 있지만, 실제 응용프로그램에서는 모놀리스 GitLab의 최상위 Gemfile이 개별 Gemfile 대신 사용될 것입니다.

이것은 젬을 위한 Gemfile에서 최상위 Gemfile과 충돌할 수 있는 어떤 버전의 의존성도 사용해서는 안 된다는 것을 의미하며, 가능하면 동일한 의존성을 사용할 수 있도록 해야 한다는 것을 의미합니다.

이것의 예는 Rack입니다. 모놀리스가 Rack 2를 사용하고 있고 우리가 Rack 3로 업그레이드하는 과정에 있을 때, 우리가 개발하는 모든 젬은 Rack 2를 테스트해야 하며, 선택적으로 CI에서 별개의 Gemfile이 사용된 경우에는 Rack 3과도 테스트해야 합니다. 여기에서 예시를 확인하세요.

이것은 Rack에만 국한되는 것이 아니라 다른 의존성에도 해당됩니다.

Gem 추출 예시

gitlab-utilsstrong_memoizeGitlab::Utils.to_boolean과 같은 GitLab 개발자가 사용하는 공통 내장 함수를 구현하는 일련의 클래스를 포함한 Gem입니다.

gitlab-database-schema-migrations은 리포지터리에 데이터베이스 마이그레이션을 개선하는 Rails 프레임워크에 대한 확장을 포함하는 잠재적인 Gem입니다. 이는 Rails에 기반을 두고 있으며 GitLab 애플리케이션에 특정하게 이루어져 있지 않으며 다른 프로젝트에서도 일반적으로 사용될 수 있거나 상류에 제공될 수 있습니다.

이전과 유사하게, gitlab-database-load-balancing은 Rails 데이터베이스 핸들링에 GitLab 특정 로드 밸런싱을 구현하기 위한 잠재적인 Gem입니다. 이것은 꽤 복잡하며 매우 특정한 코드이므로, 격리되고 테스트가 잘 된 Gem에서 복잡성을 제거하는 데 도움이 될 것입니다.

gitlab-flipper는 코드베이스에서 특징 플래그를 지원하기 위한 모든 사용자 정의 확장을 구현할 수 있는 또 다른 잠재적인 Gem입니다. 시간이 지남에 따라, 단일 코드베이스는 특징 플래그 사용을 확인하고 일관성 확인을 추가하며 추가된 특징 플래그의 소유자를 추적하는 다양한 도우미를 사용하여 성장했습니다. 이는 사실 GitLab의 비즈니스 로직의 일부가 아니며, Flipper의 구현을 더 잘 추적하고 심지어 GitLab Feature Flags를 사용하기 쉽게 변경할 수 있습니다.

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 밖에서 사용할 수 있을 것입니다. 외부 레포에서

일반적으로, 우리는 심각한 단점을 고려하기 전에 신중히 생각할 필요가 있습니다.

외부 리포지터리에 저장된 Gems은 반드시 Gemfile에서 version 구문으로 참조되어야 합니다. 항상 RubyGems에 게시되어야 합니다.

예시

GitLab에서 우리는 다음과 같은 외부 Gem을 사용합니다.

잠재적인 단점

  • 인증 프로세스와 같은 마인드에 연결되는 지속적인 단점을 인지해야 합니다. (ex: Code Review)

해당 부분은 기술적인 용어와 GitLab 특유의 용어를 정확하게 유지하기 위해 원 프로그램에 영어 용어를 그대로 사용하였습니다.

vendor/gems/

vendor/의 목적은 외부 리포지터리에서 가져오지만 간단함을 위해 모노 리포에 저장하려는 외부 의존성을 GitLab 모노리포에 통합하는 것입니다.

  • vendor/gems/은 스크립트를 통해 외부 리포지터리에서 가져오는 경우에만 사용해야 합니다.
  • vendor/gems/은 내부 gem을 저장하는 데 사용해서는 안 됩니다.
  • vendor/gems/은 GitLab 모노리포에서 빌드할 수 있도록 수정하는 것을 허용합니다.
  • gems/은 GitLab 모노리포의 일부인 모든 내부 gem을 저장하는 데 사용해야 합니다.
  • RubyGems은 GitLab 모노리포의 gems/에 없는 외부에 저장된 의존성을 위해 사용해야 합니다.

vendor/gems/의 기존 gem 처리

  • 외부 리포지터리가 없고 현재 vendor/gems/에 저장된 내부 Gem의 경우:

    • 다른 리포지터리에서 사용하는 Gems:

      • 해당 Gem을 자체 리포지터리로 마이그레이션할 것입니다.
      • RubyGems를 통해 시작하거나 계속해서 게시할 것입니다.
      • 이러한 Gems는 Gemfile에 있는 버전을 통해 참조되고 RubyGems에서 가져옵니다.
    • 모노리포에서만 사용되는 Gems:

      • RubyGems에 새 버전을 더 이상 게시하지 않을 것입니다.
      • 이미 게시된 경우 응용 프로그램이 의존할 수 있으므로 RubyGems에서 버전을 가져오지 않을 것입니다.
      • 해당 Gems은 gems/로 이동하게 됩니다.
      • 이러한 Gems는 Gemfilepath:를 통해 참조됩니다.
  • 모노리포에 있고 벤더링된 vendor/gems/의 경우:

    • 아직 상류로 올라가지 않거나 업스트림되지 않는 몇 가지 수정이 필요한 경우 리포지터리에서 유지할 것입니다.
    • 벤더링된 gem이 타사에 의해 게시될 것으로 예상됩니다.
    • 이러한 Gems은 우리에 의해 RubyGems에 발행되지 않을 것입니다.
    • 이러한 Gems은 RubyGems에 의존할 수 없으므로 Gemfilepath:를 통해 참조될 것입니다.

rubygems.org에 대한 고려 사항

gem 이름 예약

새 gem이 포함된 공개 코드를 게시하기 전에 예방 차원에서 gem 이름 예약을 할 수 있습니다. RubyGems에서 이름 스쿼터가 이름을 사용하는 것을 방지하기 위해 새로운 gem을 포함한 공개 코드를 게시하기 전에 gem 이름을 보호할 수 있습니다.

gem 이름을 예약하려면 Ruby gem 만들고 게시의 단계를 따르세요.

  • 버전으로 0.0.0을 사용합니다.
  • 내용이 raise "Reserved for GitLab"lib/NAME.rb 하나의 파일을 포함합니다.
  • buildpublish를 수행하고 성공 여부를 확인하기 위해 https://rubygems.org/gems/를 확인합니다.

계정 생성

GitLab에서의 작업 목적으로 RubyGems.org에 계정을 만들 계획이라면, 회사 이메일 계정을 사용해야 합니다.

유지자 및 계정 변경

계정 이메일 또는 암호, gem 소유자, gem 삭제와 같은 모든 변경 내용은 관련 팀에 사전에 통보되어야 합니다. (이슈나 Slack(팀의 Slack 채널, #rubygems, #ruby, #development)을 통해)