Gem 개발 가이드라인
GitLab v19.1GitLab은 모노리식 코드베이스에서 코드 재사용성과 모듈성을 향상시키기 위한 도구로 Gem을 사용합니다. 기능이 충분히 독립적이고, 다른 애플리케이션에서도 사용하고 싶거나 더 넓은 커뮤니티에 도움이 될 것으로 판단될 때 코드베이스에서 라이브러리를 분리합니다.
GitLab은 모노리식 코드베이스에서 코드 재사용성과 모듈성을 향상시키기 위한 도구로 Gem을 사용합니다.
기능이 충분히 독립적이고, 다른 애플리케이션에서도 사용하고 싶거나 더 넓은 커뮤니티에 도움이 될 것으로 판단될 때 코드베이스에서 라이브러리를 분리합니다.
코드를 Gem으로 분리하면 Gem이 애플리케이션 코드에 대한 숨겨진 의존성을 포함하지 않도록 보장할 수 있습니다.
Gem은 GitLab의 비즈니스 로직과 분리되어 독립적으로 개발 가능한 격리된 기능을 구현할 때 항상 사용해야 합니다.
Rails 코드베이스에서 새 Gem을 추출하기 가장 좋은 위치는 lib/ 폴더입니다.
lib/ 폴더에는 범용적인 코드, GitLab 전용 코드, 그리고 나머지 코드베이스와 긴밀하게 통합된 코드가 혼재되어 있습니다.
코드베이스의 일부를 Gem으로 분리할지 결정하려면 다음 질문들을 스스로에게 해보세요:
-
이 코드는 별도의 소규모 프로젝트로 구현할 수 있는 범용적이거나 보편적인 코드인가?
-
Monolith 외부의 내부 사용 환경에서도 활용될 것으로 예상되는가?
-
별도의 컴포넌트로 릴리스하는 것이 더 넓은 커뮤니티에 유용한가?
위 질문 중 하나라도 예라고 답했다면, 새 Gem 생성을 적극적으로 검토해야 합니다.
언제든지 같은 리포지터리 내에 새 Gem을 먼저 만들고, 나중에 더 넓은 커뮤니티에서 사용될 의도가 확인되면 별도 리포지터리로 마이그레이션할지 평가할 수 있습니다.
악의적인 행위자가 분리된 Gem 이름을 선점하는 것을 방지하려면 Gem 이름 예약 지침을 따르세요.
Gem 사용의 장점#
Gem을 사용하면 코드 유지 관리에 여러 가지 이점을 제공합니다:
-
코드 재사용성 - Gem은 단일 목적을 수행하는 격리된 라이브러리입니다. Gem을 사용하면 공통 기능을 잘 문서화되고 테스트된 간단한 패키지로 격리하여 다양한 애플리케이션에서 재사용할 수 있습니다.
-
모듈성 - Gem은 특정 기능을 독립적인 라이브러리로 캡슐화하여 격리를 만드는 데 도움이 됩니다. 이를 통해 코드를 더 잘 구성하고, 특정 모듈의 소유자를 더 명확히 정의하며, 특정 Gem을 유지 관리하거나 업데이트하기 쉽게 만듭니다.
-
소규모 - Gem은 설계 상 격리된 함수 집합을 구현하기 때문에 크기가 작습니다. 작은 프로젝트는 이해하고 확장하고 유지 관리하기가 훨씬 쉽습니다.
-
테스트 - Gem은 작기 때문에 모든 테스트를 훨씬 빠르게 실행하거나 Gem 테스트를 매우 철저하게 진행할 수 있습니다. Gem은 패키지화되어 자주 변경되지 않으므로, 테스트를 덜 자주 실행하여 CI 테스트 시간을 개선할 수도 있습니다.
Gem 명명#
Gem은 세 가지 경우로 분류될 수 있습니다:
-
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과gitlab-utils: 특정 컨텍스트에서 도움이 되거나 코드를 재사용하기 위한 GitLab 전용 클래스 모음.
같은 리포지터리 내에#
기존 코드베이스에서 Gem을 분리할 때는 GitLab 모노레포의 gems/에 넣으세요
이렇게 하면 Gem의 장점(모듈식 코드, 개발 시 더 빠른 테스트 실행)을 누리면서 복잡성(리포지터리 간 변경 조율, 새 권한, 여러 프로젝트 등)을 방지할 수 있습니다.
같은 리포지터리에 저장된 Gem은 Gemfile에서 path: 구문으로 참조해야 합니다.
악의적인 행위자가 분리된 Gem 이름을 선점하는 것을 방지하려면 Gem 이름 예약 지침을 따르세요.
새 Gem 생성 및 사용#
새 Gem을 추가하는 예시는 !121676에서 확인할 수 있습니다.
-
Gem 명명 규약에 따라 Gem에 적합한 이름을 선택합니다.
-
bundle gem gems/<name-of-gem> --no-exe --no-coc --no-ext --no-mit으로gems/<name-of-gem>에 새 Gem을 생성합니다. -
rm -rf gems/<name-of-gem>/.git으로gems/<name-of-gem>의.git폴더를 삭제합니다. -
rm -r gems/<name-of-gem>/sig/으로 자동 생성된 RBSsig/디렉터리를 삭제합니다. -
gems/<name-of-gem>/README.md를 편집하여 Gem에 대한 간단한 설명을 작성합니다. -
gems/<name-of-gem>/<name-of-gem>.gemspec을 편집하고 다음 예시와 같이 Gem에 대한 세부 정보를 입력합니다:
# 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
gems/<name-of-gem>/.rubocop.yml을 다음 내용으로 업데이트합니다:
inherit_from:
- ../config/rubocop.yml
- 새로 추가된 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>"
Gemfile에서 Gem을 다음과 같이 참조합니다:
gem '<name-of-gem>', path: 'gems/<name-of-gem>'
Gem의 의존성 지정#
Gem에는 자체 Gemfile이 있지만, 실제 애플리케이션에서는 Gem 디렉터리 내의 개별 Gemfile 대신 모노리식 GitLab의 최상위 Gemfile이 사용됩니다.
따라서 Gem의 Gemfile이 최상위 Gemfile과 충돌할 수 있는 버전의 의존성을 사용하지 않도록 주의해야 하며, 가능하면 동일한 의존성을 사용하도록 해야 합니다.
이의 예시로 Rack이 있습니다. 모노리스가 Rack 2를 사용하고 Rack 3으로 업그레이드 중이라면, 개발하는 모든 Gem도 Rack 2에 대해 테스트해야 하며, CI에서 별도의 Gemfile을 사용하는 경우 선택적으로 Rack 3으로도 테스트할 수 있습니다. 여기서 예시를 확인하세요.
이는 Rack에만 국한되지 않고 모든 의존성에 적용됩니다.
Gem 분리 예시#
gitlab-utils는 strong_memoize나 Gitlab::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 구현을 더 잘 추적하고 GitLab Feature Flags로 전환하기 훨씬 쉽게 만드는 데 활용될 수 있습니다.
activerecord-gitlab은 GitLab 전용 Active Record 패치를 추가하는 Gem입니다. 이러한 Gem은 복잡성을 격리하기 위해 별도로 관리되는 것이 매우 바람직합니다.
기타 잠재적 사용 사례#
gitlab-ci-config는 .gitlab-ci.yml을 파싱하는 데 사용되는 모든 CI 코드를 포함하는 잠재적 Gem입니다. 현재 이 코드는 적절한 추상화 부족으로 인해 GitLab 애플리케이션과 가볍게 연결되어 있습니다. 그러나 이를 전용 Gem으로 이전하면 GitLab 애플리케이션과의 통합을 처리하는 다양한 어댑터를 구축할 수 있습니다. 예를 들어 인터페이스는 includes:를 해석하는 어댑터를 정의할 수 있습니다. gitlab-ci-config Gem이 생기면 GitLab 내부와 GitLab Rails 외부, 그리고 GitLab CLI에서도 사용할 수 있습니다.
외부 리포지터리 내에#
일반적으로 심각한 단점이 있으므로, 이 방법을 선택하기 전에 신중하게 생각해야 합니다.
외부 리포지터리에 저장된 Gem은 Gemfile에서 반드시 version 구문으로 참조해야 합니다.
또한 반드시 RubyGems에 항상 배포해야 합니다.
예시#
GitLab에서 사용하는 외부 Gem의 예시:
잠재적 단점#
-
GitLab이 관리하는 Gem이라도 메인 Rails 애플리케이션과 동일한 코드 리뷰 프로세스를 반드시 거치지 않을 수 있습니다. 이는 애플리케이션 보안 측면에서 특히 중요합니다.
-
Danger처럼 일관된 코드 리뷰 기준을 지원하는 도구를 포함하여 CI/CD를 처음부터 설정해야 합니다.
-
코드를 별도의 프로젝트로 분리하면 기능을 변경하는 데 최소 두 개의 머지 리퀘스트가 필요합니다: 기능 변경을 위한 Gem의 머지 리퀘스트 하나와 버전을 올리기 위한 Rails 앱의 머지 리퀘스트 하나.
-
두 번째 머지 리퀘스트를 필요로 하는
gitlab-rails와의 통합 문제는 늦게 발견될 수 있습니다. -
gitlab-rails에 비해 리뷰어 및 메인테이너 풀이 작으면 코드 리뷰에 더 오랜 시간이 걸릴 수 있고 "버스 팩터"의 영향이 커집니다. -
새 Gem 버전이 어떻게 릴리스되는지에 대한 워크플로가 일관되지 않습니다. 현재는 라이브러리 메인테이너의 재량에 따라 결정됩니다.
-
코드가
gitlab-rails에 비해 가시성과 노출이 낮기 때문에 지식 사일로를 조장합니다. -
GitLab 리뷰어를 메인테이너로 승격하는 잘 정의된 프로세스가 있습니다. 그러나 분리된 라이브러리에는 이 프로세스가 적용되지 않아 코드 리뷰 기준이 낮아질 위험과 변경 사항이 배포될 위험이 높아집니다.
-
Gem의 자체 사용 목적이 더 넓은 커뮤니티의 필요와 맞지 않을 수 있습니다. 일반적으로 자체 Gem의 최신 버전을 사용하지 않는다면, 이는 경고 신호일 수 있습니다.
잠재적 장점#
-
더 작은 리포지터리에서 CI/CD가 실행되므로 더 빠른 피드백 루프를 제공합니다.
-
더 넓은 커뮤니티에 프로젝트를 노출하고 외부 기여자의 혜택을 받을 수 있습니다.
-
리포지터리 소유자가 변경 사항을 리뷰하기에 가장 적합한 대상일 가능성이 높으므로,
gitlab-rails에서 올바른 리뷰어를 찾는 수고를 덜 수 있습니다.
Ruby Gem 생성 및 배포#
새 Gem의 프로젝트는 항상 gitlab-org/ruby/gems 네임스페이스에 생성해야 합니다:
-
Gem에 적합한 이름을 결정합니다. GitLab 소유의 Gem이라면 Gem 이름에
gitlab-접두사를 붙입니다. 예:gitlab-sidekiq-fetcher. -
로컬에서 Gem을 생성하거나 필요에 따라 포크합니다.
-
rubygems.org에 빈
0.0.1버전을 배포하여 Gem 이름을 예약합니다. -
다음 명령을 실행하여
gitlab_rubygems사용자를 새 Gem의 소유자로 추가합니다:
gem owner <gem-name> --add gitlab_rubygems
비공개 RubyGems 위원회 프로젝트에서 소유권을 확인하려면 Rémy Coutable에게 알립니다.
- 선택 사항. 다음 사용자 중 일부 또는 전부를 공동 소유자로 추가합니다:
-
선택 사항. 관련된 다른 개발자를 공동 소유자로 추가합니다.
-
https://rubygems.org/gems/<gem-name>을 방문하여 Gem이 성공적으로 배포되었는지,gitlab_rubygems도 소유자인지 확인합니다. -
gitlab-org/ruby/gems그룹 또는 하위 그룹에 프로젝트를 생성합니다:
새 프로젝트에 대한 지침을 따릅니다.
-
CI/CD 설정을 위한 지침을 따릅니다.
-
다음을
.gitlab-ci.yml에 추가하여gem-releaseCI 컴포넌트로 새 Gem 버전을 릴리스하고 배포합니다:
include:
- component: $CI_SERVER_FQDN/gitlab-org/components/gem-release/gem-release@~latest
이 job은 Gem 빌드 및 배포(Gem 패키지를 배포하기 위해 gitlab-org/ruby/gems 그룹에서 상속된 gitlab_rubygems RubyGems API 토큰을 사용)를 처리하고, 변경 로그 데이터 생성 API 엔드포인트를 사용하여 태그, 릴리스를 생성하고 릴리스 노트를 채웁니다.
변경 로그 항목 파일을 언제, 어떻게 생성하는지에 대한 지침은 전용 변경 로그 항목 페이지를 참조하세요.
GitLab 프로젝트와의 일관성을 위해,
Gem 프로젝트는 gitlab-styles Gem과 동일한 내용으로 .gitlab/changelog_config.yml에 변경 로그 YAML 설정 파일을 정의할 수도 있습니다.
-
릴리스 프로세스를 간소화하기 위해 gitlab-styles Gem과 동일한 내용으로
.gitlab/merge_request_templates/Release.md머지 리퀘스트 템플릿을 생성할 수도 있습니다 (gitlab-styles를 실제 Gem 이름으로 교체해야 합니다). -
프로젝트 배포 지침을 따릅니다.
참고: 경우에 따라 Gem을 자체 네임스페이스로 이전하고 싶을 수 있습니다. 예를 들어, 자연스럽게 여러 프로젝트를 갖게 되는 경우(별도의 라이브러리로 플러그인을 갖는 경우 등)나 GitLab 팀원뿐만 아니라 외부 사용자도 이 프로젝트의 메인테이너가 될 것으로 예상되는 경우입니다. 후자의 상황(외부 메인테이너)은 현재 GitLab에 재직 중인 사람이 GitLab을 떠난 후에도 Gem을 계속 유지 관리하고자 하는 경우에도 해당될 수 있습니다.
vendor/gems/#
vendor/의 목적은 외부 리포지터리를 가지고 있지만 단순성을 위해 모노레포에 저장하고 싶은 외부 의존성을 GitLab 모노레포로 가져오는 것입니다:
-
vendor/gems/는 스크립트 또는 수동으로 외부 리포지터리에서 가져오는 경우에만 사용해야 합니다. -
vendor/gems/는 사내 Gem 저장에 사용해서는 안 됩니다. -
vendor/gems/는 GitLab 모노레포에서 빌드 가능하도록 수정 사항을 허용할 수 있습니다. -
gems/는 GitLab 모노레포의 일부인 모든 사내 Gem을 저장하는 데 사용해야 합니다. -
RubyGems는 GitLab 모노레포의
gems/에 없는 모든 외부 저장 의존성에 사용해야 합니다.
vendor/gems에 있는 기존 Gem 처리#
- 외부 리포지터리가 없고 현재
vendor/gems/에 저장된 사내 Gem의 경우:
다른 리포지터리에서 사용되는 Gem:
자체 리포지터리로 마이그레이션합니다.
-
RubyGems를 통한 배포를 시작하거나 계속합니다.
-
해당 Gem은
Gemfile에서 버전으로 참조되고 RubyGems에서 가져옵니다. -
모노레포에서만 사용되는 Gem의 경우:
RubyGems에 새 버전 배포를 중단합니다.
-
다른 애플리케이션이 이미 배포된 버전에 의존하고 있을 수 있으므로 RubyGems에서 이미 배포된 버전은 가져오지 않습니다.
-
해당 Gem을
gems/로 이전합니다. -
해당 Gem은
Gemfile에서path:로 참조됩니다. -
외부에서 모노레포에 벤더링된
vendor/gems/의 경우:
업스트림되지 않았거나 아직 업스트림되지 않은 수정이 필요한 경우 리포지터리에서 유지 관리합니다.
-
벤더링된 Gem은 서드파티에 의해 배포될 수 있습니다.
-
해당 Gem은 우리가 RubyGems에 배포하지 않습니다.
-
RubyGems에 의존할 수 없으므로 해당 Gem은
Gemfile에서path:로 참조됩니다.
rubygems.org 관련 고려 사항#
Gem 이름 예약#
새 Gem을 포함하는 공개 코드를 배포하기 전에 Gem 이름을 미리 예약하여 RubyGems에서 이름 선점자가 이름을 차지하는 것을 방지할 수 있습니다.
Gem 이름을 예약하려면 Ruby Gem 생성 및 배포 단계를 따르되, 다음과 같이 변경합니다:
-
버전으로
0.0.0을 사용합니다. -
raise "Reserved for GitLab"내용이 담긴 단일 파일lib/NAME.rb를 포함합니다. -
build와publish를 수행하고 https://rubygems.org/gems/에서 성공 여부를 확인합니다.
계정 생성#
GitLab 업무를 위해 RubyGems.org에 계정을 생성하는 경우:
-
회사 이메일 계정(
@gitlab.com)을 사용합니다. -
YubiKey 또는 기기 패스키와 같은 Web Authentication 기기를 사용하여 이중 인증을 설정합니다.
메인테이너 및 계정 변경#
계정 이메일이나 비밀번호 수정, Gem 소유자, Gem 삭제 등의 모든 변경 사항은 이슈 또는 Slack(#rubygems, #ruby, #development 등 팀의 Slack 채널)을 통해 직접 책임 팀에 사전에 알려야 합니다.