Gems 개발 가이드라인
GitLab은 모놀리식 코드베이스에서 코드의 재사용성과 모듈화를 향상시키기 위한 도구로서 Gems를 사용합니다.
기능이 매우 격리되어 다른 응용프로그램에서 사용하려는 우리만의 목적으로 라이브러리를 추출합니다. 또는 보다 넓은 커뮤니티에 이로움을 줄 것으로 기대하는 경우에도 라이브러리를 추출합니다.
절대 우리의 애플리케이션 코드에 대한 숨겨진 의존성을 포함하지 않도록 Gem에 코드를 추출하는 것도 보장합니다.
Gems는 항상 격리되고 GitLab의 비즈니스 로직과 분리된 기능을 구현할 때 사용해야 합니다.
새로운 Gems를 추출할 수 있는 기회가 가장 많은 곳은 lib/ 폴더입니다.
우리의 lib/ 폴더는 일반적이고, 보편적인 코드, GitLab에 특화된 코드, 그리고 코드베이스의 나머지 부분과 긴밀하게 통합된 코드의 혼합체입니다.
코드베이스 일부를 Gem으로 추출할지 결정하기 위해 다음 질문에 대한 답변을 스스로에게 물어보세요:
- 이 코드는 별도의 작은 프로젝트로 수행할 수 있는 일반적이거나 보편적인가요?
- 나는 이 코드를 모놀리식에서 외부적으로 사용할 것으로 기대하고 있나요?
- 우리가 별도의 컴포넌트로 출시할 것으로 고려해야 하는 더 넓은 커뮤니티에 유용한가요?
위의 질문 중 예라고 대답한다면 새로운 Gem을 만들 것을 강력히 고려해야 합니다.
새로운 Gem을 생성할 때는 항상 같은 리포지터리에서 새 Gem을 만들고 나중에 보다 넓은 커뮤니티에서 사용될 때 별도 리포지터리로 마이그레이션할지 여부를 평가하세요.
Gems 사용의 이점
Gems를 사용하는 것은 코드 유지보수에 여러 가지 이점을 제공할 수 있습니다:
-
코드 재사용성 - Gems는 단일 목적을 제공하는 격리된 라이브러리입니다. Gems를 사용하면 공통 기능을 단순한 패키지로 격리시켜 문서화하고 테스트하여 다른 응용프로그램에서 재사용할 수 있습니다.
-
모듈성 - Gems는 특정 기능을 캡슐화하여 격리시킴으로써 분리를 만들어줍니다. 이는 코드를 더 잘 구성하고 누가 특정 모듈의 소유자인지 더 잘 정의하며 특정 Gems를 유지 또는 업데이트하기 쉽게 만들어줍니다.
-
작음 - 격리된 함수 세트를 구현함으로써 Gems는 디자인 상으로 작습니다. 작은 프로젝트는 이해하고 확장 및 유지하기 훨씬 쉽습니다.
-
테스트 - 작은 규모의 Gems를 사용하면 테스트를 빠르게 실행하거나 Gem의 테스트를 매우 철저하게 진행하는 것이 훨씬 빨라집니다. Gem이 패키지로 제공되고 자주 변경되지 않기 때문에 테스트를 덜 자주 실행하여 CI 테스트 시간을 개선할 수도 있습니다.
Gem 이름 지정
Gems는 세 가지 다른 경우로 나뉩니다:
-
unique_gem
: Gem에 GitLab과 관련된 내용이 없다면 gem 이름에gitlab
을 포함시키지 마세요. -
existing_gem-gitlab
: 공개적으로 사용 가능한 gem을 포크하고 수정/확장하는 경우, Rubygems 규칙에 따라-gitlab
접미사를 추가하세요. -
gitlab-unique_gem
: GitLab 프로젝트의 문맥에서만 유용한 Gems에는gitlab-
접두어를 포함시키세요.
기존 Gem의 예시:
-
y-rb
: Yrs의 Ruby 바인딩입니다. Yrs “wires”는 Yjs 프레임워크의 Rust 포트입니다. -
activerecord-gitlab
:activerecord
공개 Gem에 GitLab에 특화된 패치를 추가합니다. -
gitlab-rspec
및gitlab-utils
: 특정 컨텍스트에서 도움이 되는 GitLab 특화 클래스 또는 코드를 재사용하기 위한 GitLab-특화 세트입니다.
같은 리포지터리에 있는 경우
기존 코드베이스에서 Gems를 추출할 때, GitLab monorepo의 gems/
에 넣으세요.
그렇게 하면 Gems의 장점(모듈화된 코드, 개발 중에 빠르게 테스트 실행)을 얻으면서도 복잡성(리포지터리 간의 변경 조정, 새 권한, 여러 프로젝트 등)을 방지할 수 있습니다.
동일한 리포지터리에 저장된 Gems는 Gemfile
에 path:
구문을 사용하여 참조해야 합니다.
새로운 Gem 생성 및 사용
새로운 Gem을 추가하는 예시를 확인할 수 있습니다: !121676.
- Gem naming 규칙을 따라 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
폴더를 제거하세요. -
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에서 테스트되어야 하며, 별도의 Gemfile
이 CI에서 사용된다면 Rack 3에서도 테스트되어야 합니다. 여기에서 예제를 확인하세요.
이것은 Rack에 국한되는 것이 아니라 모든 의존성에 적용됨을 기억해주세요.
Gem 추출의 예
gitlab-utils
는 strong_memoize
나 Gitlab::Utils.to_boolean
과 같은 GitLab 개발자들이 사용하는 공통 내재기능을 구현한 클래스 세트를 포함하는 Gem입니다.
gitlab-database-schema-migrations
은 리포지터리에 있는 데이터베이스 마이그레이션 저장 방식을 개선하는 Rails 프레임워크에 대한 확장 기능을 포함하는 잠재적인 Gem입니다. 이는 GitLab 응용프로그램에 특정하지 않고 다른 프로젝트에서도 사용하거나 상위 버전으로 업스트림될 수 있습니다.
gitlab-database-load-balancing
은 이전과 유사하게 GitLab 특정 데이터베이스 로드 밸런싱을 구현하는 잠재적인 Gem입니다. 이것은 꽤 복잡하며 매우 특정한 코드이므로 별도로 테스트된 Gem에서 복잡성을 격리화하면 대형 단일 코드베이스의 복잡성을 줄일 수 있습니다.
gitlab-flipper
는 코드베이스에서 피처 플래그를 지원하기 위한 모든 사용자 정의 확장을 구현하는 또 다른 잠재적인 Gem입니다. 시간이 지남에 따라 코드베이스에서 피처 플래그 사용을 확인하고 일관성 검사 및 추가된 피처 플래그 소유자를 추적하기 위한 다양한 도구가 추가되었습니다. 이것은 실제로 GitLab의 비즈니스 로직에 속하지 않으며 GitLab Feature Flags로 전환하여 구현을 훨씬 쉽게 변경할 수 있습니다.
activerecord-gitlab
은 GitLab 특정 Active Record 패치를 추가한 Gem입니다. 복잡성을 격리하기 위해 이를 별도로 관리하는 것이 매우 바람직합니다.
기타 잠재적인 사용 사례
gitlab-ci-config
는 .gitlab-ci.yml
을 구문 분석하는 데 사용되는 CI 코드를 모아둔 잠재적인 Gem입니다. 이 코드는 적절한 추상화 부재로 인해 현재 GitLab 응용프로그램과 약간 얽혀 있습니다. 그러나, gitlab-ci-config
를 별도로 이동시키면 GitLab 응용프로그램과의 통합을 처리하는 다양한 어댑터를 구축하는 것이 가능해질 수 있습니다. 인터페이스는 예를 들어 includes:
를 해결하는 어댑터를 정의할 것입니다. gitlab-ci-config
Gem이 있다면 GitLab 내외에서 사용할 수 있을 것입니다.
외부 리포지터리에서
일반적으로 우리는 심각한 단점이 있기 때문에 이를 신중히 고려해야 합니다.
외부 리포지터리에 저장된 Gem은 반드시 version
구문으로 Gemfile
에 참조되어야 합니다.
항상 RubyGems에 발행되어야 합니다.
예시
GitLab에서 다음과 같은 외부 Gem을 사용합니다:
잠재적인 단점
- 다른 기본 레일 애플리케이션과 동일한 코드 리뷰 프로세스를 거치지 않는다고 해도 GitLab에서 유지보수되는 Gem조차도 그렇지 않을 수 있습니다. 이는 응용프로그램 보안에 매우 중요합니다.
- Danger와 같은 일관된 코드 리뷰 표준을 지원하는 등 CI/CD를 처음부터 설정해야 합니다.
- 코드를 별도 프로젝트로 이동하는 것은 기능을 변경하기 위한 최소한 두 개의 합병 요청이 필요하다는 것을 의미합니다: 기능 변경을 위해 gem에서 하나, 버전을 업데이트하기 위해 레일 앱에서 하나.
- 두 번째 MR이 필요한
gitlab-rails
통합은 통합 문제가 늦게 발견될 수 있음을 의미합니다. -
gitlab-rails
에 비해 리뷰어와 유지보수자의 수가 적기 때문에 코드를 검토하는 시간이 더 오래 걸릴 수 있으며 “버스 팩터”의 영향이 커질 수 있습니다. - 새로운 Gem 버전의 릴리스를 위한 일관된 작업 흐름이 없습니다. 현재로서 라이브러리 유지자가 작동 방식을 결정하는 것이어야 합니다.
- 코드의 가시성과 노출을 감소시켜 지식 실로를 촉진할 수 있습니다.
- GitLab 리뷰어가 유지자로 승격하는 것에 대한 명확한 프로세스가 있습니다. 그러나 추출된 라이브러리에는 그러한 프로세스가 없으므로 코드 리뷰의 기준을 낮추게 될 위험이 있으며 변경 사항을 출시할 위험이 증가합니다.
- 우리가 사용하는 Gem의 최신 버전이 아니라는 것은 일반적으로 경고의 신호일 수 있습니다.
잠재적인 이점
- 더 작은 리포지터리를 대상으로 하므로 더 빠른 피드백 루프를 제공합니다.
- 더 넓은 커뮤니티에 프로젝트를 노출하고 외부 기여를 수용하는 능력입니다.
- 리포지터리 소유자가 변경 사항을 검토하는 데 가장 적합한 관객이기 때문에
gitlab-rails
에서 적절한 리뷰어를 찾는 필요성이 감소합니다.
Ruby Gem 만들고 발행하기
새로운 Gem을 위한 프로젝트는 항상 gitlab-org/ruby/gems
네임스페이스에 생성되어야 합니다.
- Gem에 적합한 이름을 결정합니다. GitLab 소유의 Gem이라면 Gem 이름에
gitlab-
을 접두사로 붙여야 합니다. 예를 들면,gitlab-sidekiq-fetcher
. - 필요에 따라 지역적으로 Gem을 만들거나 포크합니다.
-
rubygems.org에 빈
0.0.1
버전의 Gem을 발행하여 Gem 이름이 예약되었는지 확인합니다. -
다음과 같이 실행하여 새 Gem에
gitlab_rubygems
사용자를 소유자로 추가합니다:gem owner <gem-name> --add gitlab_rubygems
- 선택 사항. 다음 사용자 중 일부 또는 전부를 공동 소유자로 추가합니다:
- 관련 개발자를 선택적으로 공동 소유자로 추가합니다.
-
https://rubygems.org/gems/<gem-name>
을 방문하여 Gem이 성공적으로 발행되었는지 확인하고gitlab_rubygems
가 소유자인지 확인합니다. -
gitlab-org/ruby/gems
그룹에 프로젝트를 생성합니다:- 새로운 프로젝트에 대한 지침을 따릅니다.
- CI/CD 구성을 설정하는 지침을 따릅니다.
-
.gitlab-ci.yml
에 다음을 추가하여 새 Gem 버전을 릴리스하고 발행하기 위해gem-release
CI 구성을 사용합니다:include: - component: gitlab.com/gitlab-org/components/gem-release/gem-release@~latest
이 작업은 Gem을 빌드하고 발행하며, 릴리스 태그를 생성하고 발행 노트를 생성하며, 변경 내용 데이터를 생성하여 사용합니다.
변경 내용 항목 파일을 생성하는 시기와 방법에 대한 지침은 전용 Changelog entries 페이지를 참조하십시오. GitLab 프로젝트와 일관성을 유지하게 위해서, Gem 프로젝트들도
.gitlab/changelog_config.yml
에 동일한 내용의 변경 내용 YAML 구성 파일을 정의할 수 있을 것입니다. - 릴리스 프로세스를 단순화하기 위해
.gitlab/merge_request_templates/Release.md
MR 템플릿을 생성할 수 있습니다. gitlab-styles (실제 gem 이름으로gitlab-styles
를 대체해야 함)의 내용과 동일하게 만듭니다. - 프로젝트를 발행하는 방법을 따릅니다.
참고: 어떤 경우에는 Gem을 자체 네임스페이스로 이동하고 싶을 수 있습니다. 이런 경우에는 별도의 라이브러리로서가 아니라 별도의 프로젝트로서 연결점이 두 개 이상이 될 것으로 기대됩니다. 또는 GitLab 팀 구성원 이외의 사용자가 이 프로젝트를 유지할 것으로 예상하는 경우에도 해당됩니다. 후자의 경우 (GitLab 이외의 유지보수자)는 현재 GitLab에서 일하는 누군가가 GitLab에서 일하는 동안 이 프로젝트를 유지하고자 할 때도 해당됩니다.
vendor/gems/
vendor/
의 목적은 외부 리포지터리를 가지고 있지만 단순함을 위해 모노레포에 저장하고 싶은 외부 의존성을 GitLab 모노레포로 가져오는 것입니다.
-
vendor/gems/
는 오직 스크립트를 통해 외부 리포지터리에서 또는 매뉴얼으로 가져올 때만 사용해야 합니다. -
vendor/gems/
는 내부 젬을 저장하는 데 사용해서는 안 됩니다. -
vendor/gems/
는 GitLab 모노레포에서 빌드할 수 있도록 수정하는 것을 허용할 수 있습니다. -
gems/
는 GitLab 모노레포의 일부인 모든 내부 젬을 저장하는 데 사용해야 합니다. -
RubyGems는 GitLab 모노레포의
gems/
에 없는 외부 저장 의존성에 사용되어야 합니다.
기존 vendor/gems/
의 처리
-
현재
vendor/gems/
에 저장되어 있는 내부 리포지터리가 없고 외부 리포지터리에서 가져오는 내부 Gems의 경우:-
다른 리포지터리에서 사용되는 Gems의 경우:
- 해당 Gems를 고유한 리포지터리로 이전할 것입니다.
- RubyGems를 통해 발행을 시작하거나 계속할 것입니다.
- 해당 Gems는
Gemfile
에서 버전으로 참조되고 RubyGems에서 가져올 것입니다.
-
모노레포에서만 사용되는 Gems의 경우:
- 새 버전은 RubyGems에 발행하지 않을 것입니다.
- 이미 게시된 RubyGems에서 가져오지 않을 것입니다. 응용 프로그램이 이에 의존할 수 있기 때문입니다.
- 해당 Gems를
gems/
로 이동할 것입니다. - 해당 Gems는
Gemfile
의path:
를 통해 참조될 것입니다.
-
-
모노레포에 외부에서 vendored된
vendor/gems/
의 경우:- 이미 upstream되지 않거나 아직 upstream되지 않은 몇 가지 수정이 필요한 경우 해당 Gems를 리포지터리에 유지할 것입니다.
- vendored Gems가 타사에 의해 발행되는 것으로 예상됩니다.
- 해당 Gems는 우리가 RubyGems에 발행하지 않을 것입니다.
- 해당 Gems는 RubyGems에 의존할 수 없기 때문에
Gemfile
의path:
를 통해 참조될 것입니다.
젬 이름 예약
새로운 젬을 포함하는 공개 코드를 발행하기 전에 젬 이름을 사전 예방 차원에서 RubyGems의 이름 빼앗는 사람을 피하기 위해 예약합니다.
젬 이름을 예약하려면 Ruby gem 생성 및 게시 단계를 따르되, 다음과 같은 변경 사항을 적용하세요:
- 버전으로
0.0.0
을 사용합니다. - 내용이
lib/NAME.rb
인 단일 파일lib/NAME.rb
을 포함하고 내용은raise "Reserved for GitLab"
으로 합니다. -
build
및publish
를 수행하고 성공 여부를 확인하기 위해 https://rubygems.org/gems/를 확인하세요.