Gems 개발 가이드라인
GitLab은 모놀리식 코드베이스에서 코드 재사용성과 모듈성을 향상시키는 도구로 Gems를 사용합니다.
우리는 코드베이스에서 라이브러리를 추출할 때 해당 기능이 매우 격리되어 있고 다른 응용 프로그램에서 사용하거나 보다 넓은 커뮤니티에 이로운 경우에 사용하고 싶을 때 추출합니다.
Gem으로 코드를 추출하면 해당 Gem에 우리의 응용프로그램 코드에 대한 숨겨진 종속성이 없음을 보장합니다.
Gems는 항상 GitLab의 비즈니스 로직과 독립적이며 별도로 개발이 가능한 기능을 구현할 때 사용해야 합니다.
새로운 gems를 추출할 수 있는 가장 좋은 장소는 lib/ 폴더입니다.
lib/ 폴더는 일반적이거나 보편적이며 GitLab 특정, 그리고 코드베이스의 나머지 부분과 밀접하게 통합된 코드의 혼합입니다.
코드베이스의 일부를 Gem으로 추출해야 할지 결정하려면 다음 질문에 답해보세요:
- 이 코드는 별도의 작은 프로젝트로 수행할 수 있는 일반적이거나 보편적인가요?
- 이 코드를 모놀리식 애플리케이션 외부에서 사용할 것으로 기대하고 있나요?
- 우리가 별도의 구성요소로 출시할만큼 넓은 커뮤니티에 유용한가요?
위 질문 중 어느 하나에 예라는 답이라면 새로운 Gem을 만드는 것을 강력히 고려해야 합니다.
새로운 Gem을 만들 때는 항상 같은 저장소에 새로운 Gem을 생성한 후 나중에 넓은 커뮤니티에서 사용할 때 별도의 저장소로 이전하는 것을 평가합니다.
경고: 악의적인 사용자가 추출된 Gems의 이름을 차지하지 못하도록 하려면 gem 이름을 예약하는 지침을 따르세요.
Gems 사용의 장점
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 프로젝트의 문맥에서만 유용한 gem에는gitlab-
접두어를 포함시킵니다.
기존 gems의 예:
-
y-rb
: Yrs의 루비 바인딩입니다. Yrs “wires”는 Yjs 프레임워크의 Rust 포트입니다. -
activerecord-gitlab
:activerecord
공개 gem에 GitLab 특정 패치를 추가합니다. -
gitlab-rspec
및gitlab-utils
: 특정 컨텍스트에서 도움이 되는 GitLab 특정 클래스 집합 또는 코드 재사용입니다.
같은 저장소에
기존 코드베이스에서 gems를 추출할 때는 GitLab 모노 레포지토리의 gems/
폴더에 넣습니다.
이렇게 하면 gems를 사용하는 이점을 얻을 수 있습니다(모듈식 코드, 개발 중에 테스트를 빨리 실행함)과 복잡성(저장소 간의 변경 조정, 새 권한, 여러 프로젝트 등)을 방지할 수 있습니다.
같은 저장소에 저장된 Gems는 Gemfile
에서 path:
구문을 사용하여 참조해야 합니다.
경고: 악의적인 사용자가 추출된 Gems의 이름을 차지하지 못하도록 하려면 gem 이름을 예약하는 지침을 따르세요.
새로운 Gem 생성 및 사용
새로운 gem을 추가하는 예제를 아래에서 볼 수 있습니다: !121676.
- Gem 이름 규칙을 따라 새 gem에 좋은 이름을 선택하세요.
- 다음 명령을 사용하여
gems/<gem-이름>
에 새로운 gem을 생성하세요:bundle gem gems/<gem-이름> --no-exe --no-coc --no-ext --no-mit
. -
gems/<gem-이름>
폴더에서.git
폴더를 제거하세요:rm -rf gems/<gem-이름>/.git
. -
gems/<gem-이름>/README.md
를 편집하여 Gem의 간단한 설명을 제공하세요. -
gems/<gem-이름>/<gem-이름>.gemspec
을 편집하고 다음과 같이 Gem에 대한 세부 정보를 기재하세요:# frozen_string_literal: true require_relative "lib/name/of/gem/version" Gem::Specification.new do |spec| spec.name = "<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/<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/<gem-이름>/.rubocop.yml
을 업데이트하고 다음 내용으로 설정하세요:inherit_from: - ../config/rubocop.yml
-
새로 추가된 Gem을 위해 CI를 구성하세요:
-
gems/<gem-이름>/.gitlab-ci.yml
를 추가하세요:include: - local: gems/gem.gitlab-ci.yml inputs: gem_name: "<gem-이름>"
-
.gitlab/ci/gitlab-gems.gitlab-ci.yml
에 다음을 추가하세요:include: - local: .gitlab/ci/templates/gem.gitlab-ci.yml inputs: gem_name: "<gem-이름>"
-
-
Gemfile
에서 Gem을 다음과 같이 참조하세요:gem '<gem-이름>', path: 'gems/<gem-이름>'
Gem에 대한 종속성 지정
Gem의 경우 자체 Gemfile
이 있지만, 실제 응용 프로그램에서는 모노리스 GitLab의 최상위 Gemfile
이 개별 Gemfile
대신 사용됩니다.
이것은 Gem 디렉토리에 있는 개별 Gemfile
과 충돌할 수 있는 종속성 버전을 사용하지 않아야 하며 가능한 경우 동일한 종속성을 사용해야 한다는 점을 인식해야 합니다.
이에 대한 예시로 Rack이 있습니다. 모노리스에서 Rack 2를 사용하고 있고 Rack 3로 업그레이드하는 과정에 있다면, 개발하는 모든 Gem은 Rack 2와 충돌이 없도록 테스트되어야 하며, 필요하다면 CI에서 별도의 Gemfile
을 사용하여 Rack 3에 대해서도 테스트되어야 합니다. 아래의 예시를 참조하세요.
이것은 Rack에만 해당하는 것은 아니라 다른 종속성에도 해당됩니다.
Gem 추출 예시
gitlab-utils
는 GitLab 개발자들이 사용하는 공통 내부 함수를 구현하는 클래스 집합을 포함하는 Gem입니다. 여기에는 strong_memoize
나 Gitlab::Utils.to_boolean
과 같은 함수가 포함됩니다.
gitlab-database-schema-migrations
은 저장소에 데이터베이스 마이그레이션을 저장하는 방법을 개선하는 Rails 프레임워크의 확장 기능을 포함하는 잠재적인 Gem입니다. 이는 GitLab 애플리케이션에 특정하게 사용되는 것이 아니며, 다른 프로젝트에서도 사용하거나 상류로 upstream될 수 있습니다.
이전과 유사하게, gitlab-database-load-balancing
또한 GitLab 특정 데이터베이스 로드 밸런싱을 구현하는 잠재적인 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 내부 및 외부에서 사용할 수 있을 것입니다.
외부 리포지토리에서
일반적으로, 심각한 단점이 있기 때문에 이를 신중하게 생각해야 합니다.
외부 리포지토리에 저장된 Gem은 반드시 version
구문을 사용하여 Gemfile
에 참조되어야 합니다. 또한 항상 RubyGems에 발행되어야 합니다.
예시
GitLab에서는 여러 외부 Gem을 사용합니다:
잠재적인 단점
- GitLab에서 유지보수하는 Gem이라도 주요 Rails 애플리케이션과 동일한 코드 검토 프로세스를 반드시 거쳐가지 않습니다. 특히 응용 프로그램 보안에 대해서는 이것이 매우 중요합니다.
- 일관된 코드 검토 표준을 지원하는 Danger와 같은 도구를 비롯해 CI/CD 설정을 처음부터 해야 합니다.
- 코드를 별도 프로젝트로 추출하면 기능을 변경하기 위해 최소한 두 개의 머지 리퀘스트가 필요하게 됩니다: 기능 변경을 위한 Gem에서의 리퀘스트와 버전을 올리기 위한 Rails 앱에서의 머지 리퀘스트입니다.
-
gitlab-rails
와의 통합을 위한 두 번째 MR이 필요하다는 것은 통합 문제가 늦게 발견될 수 있다는 것을 뜻합니다. -
gitlab-rails
에 비해 리뷰어 및 유지보수자 풀이 작기 때문에 코드 검토 및 “버스 요인”의 영향을 받을 가능성이 높아집니다. - 새로운 Gem 버전이 어떻게 릴리스되는지에 대한 일관되지 않은 워크플로우가 있습니다. 현재 이것은 라이브러리 유지자가 어떻게 작동하는지를 결정하는 재량에 따라 이루어집니다.
- 코드의 가시성과 노출이
gitlab-rails
보다 적기 때문에 지식 실로를 촉진합니다. - 우리는 GitLab 리뷰어를 유지보수자로 승진시키기 위한 명확한 프로세스가 있습니다. 그러나 추출된 라이브러리에 대해서는 이것이 적용되지 않아 코드 검토 기준이 낮아질 가능성과 변경사항을 배포할 위험이 높아집니다.
- 우리의 Gem 사용 욕구가 널리 알려진 커뮤니티의 욕구와 일치하지 않을 수 있습니다. 일반적으로, 우리가 우리의 Gem의 최신 버전을 사용하고 있지 않으면, 그것은 경고 신호일 수 있습니다.
잠재적 이점
- 보다 신속한 피드백 루프, CI/CD가 작은 저장소에 대해 실행되기 때문입니다.
- 프로젝트를 보다 넓은 커뮤니티에 노출시키고 외부 기여에서 이점을 얻을 수 있습니다.
- 변경 사항을 검토하는 가장 적합한 대상인 저장소 소유자를 찾는 필요성이 감소합니다.
gitlab-rails
에서 적절한 리뷰어를 찾는 필요성이 줄어듭니다.
루비 젬 생성 및 게시
새로운 젬을 위한 프로젝트는 언제나 gitlab-org/ruby/gems
네임스페이스에 생성해야 합니다:
- 젬에 적합한 이름을 결정합니다. GitLab 소유의 젬이라면 젬 이름에
gitlab-
을 접두어로 붙입니다. 예를 들어,gitlab-sidekiq-fetcher
. - 필요에 따라 젬을 로컬에서 생성하거나 포크합니다.
- 젬의 이름이 예약되었는지 확인하기 위해 rubygems.org에 빈
0.0.1
버전의 젬을 등록합니다. -
다음을 실행하여 새로운 젬에
gitlab_rubygems
사용자를 소유자로 추가합니다.gem owner <gem-name> --add gitlab_rubygems
- 선택 사항. 다음 사용자 중 일부 또는 모두를 공동 소유자로 추가합니다:
- 선택 사항. 그 외 관련 개발자를 공동 소유자로 추가합니다.
-
https://rubygems.org/gems/<gem-name>
을 방문하여 젬이 성공적으로 게시되었는지 확인하고gitlab_rubygems
가 소유자로 등록되었는지 확인합니다. -
gitlab-org/ruby/gems
그룹에서 프로젝트를 생성합니다:- 새 프로젝트 지침을 따릅니다.
- CI/CD 구성 설정 지침을 따릅니다.
-
다음을
.gitlab-ci.yml
에 추가하여gem-release
CI 구성 요소를 사용하여 새로운 젬 버전을 릴리스하고 게시합니다:include: - component: gitlab.com/gitlab-org/components/gem-release/gem-release@~latest
이 작업은 젬을 빌드하고 게시하는 작업을 처리하며(게시된 젬 패키지를 위해
gitlab_rubygems
Rubygems.org API 토큰을 사용하여 그룹에서 상속받음), 태그를 생성하고 릴리스하며 변경 로그 데이터 생성 API 엔드포인트를 사용하여 릴리스 노트를 작성합니다.언제 어떻게 변경 로그 항목 파일을 생성하는 지침은 변경 로그 항목 페이지를 참조하십시오. GitLab 프로젝트에 일관성 유지하기 위해, 젬 프로젝트는 또한 gitlab-styles 젬과 동일한 내용의
.gitlab/changelog_config.yml
변경 로그 YAML 구성 파일을 정의할 수 있습니다. - 릴리스 프로세스를 용이하게 하기 위해,
.gitlab/merge_request_templates/Release.md
MR 템플릿을 gitlab-styles 젬에서와 동일한 내용으로 생성할 수 있습니다(실제 젬 이름으로gitlab-styles
를 대체해야 합니다). - 프로젝트 게시 지침을 따릅니다.
참고: 어떤 경우에는 젬을 자체 네임스페이스로 이동하길 원할 수 있습니다. 일부 예시는 더 많은 프로젝트를 가지고 있을 것으로 예상될 때(예: 별도의 라이브러리로 플러그인이있는 경우)이거나 GitLab 외부 사용자가 유지 관리자로 있을 것으로 예상되는 경우입니다. 후자의 상황(외부 GitLab 사용자가 유지 관리자일 것으로 예상되는 경우)은 현재 GitLab에서 작업하는 사람이 GitLab에서의 근무 시간 이후에도 젬을 유지보수할 것으로 예상되는 경우에도 적용될 수 있습니다.
vendor/gems/
vendor/
의 목적은 외부 레포지토리를 가지고 있지만 단순함을 위해 GitLab 모노리포로 가져오는 외부 종속성을 담는 것입니다.
-
vendor/gems/
는 반드시 스크립트를 통해 외부 레포지토리에서 가져올 때만 사용해야 합니다. -
vendor/gems/
는 사내 젬을 저장하는 데 사용해서는 안됩니다. -
vendor/gems/
는 GitLab 모노리포에서 빌드할 수 있도록 수정 사항을 허용합니다. -
gems/
는 GitLab 모노리포의 일부인 모든 사내 젬을 저장하는 데 사용해야 합니다. -
RubyGems는 GitLab 모노리포의
gems/
에 없는 외부 저장 종속성에 사용해야 합니다.
vendor/gems/
에 있는 기존 젬 처리
-
현재
vendor/gems/
에 저장된 외부 레포지토리가 없고 사내 젬인 경우:-
다른 레포지토리에서 사용되는 사내 젬:
- 자체 레포지토리로 이동할 것입니다.
- RubyGems를 통해 공개를 시작하거나 계속 진행할 것입니다.
- 이러한 젬은
Gemfile
의 버전으로 참조되고 RubyGems에서 가져올 것입니다.
-
모노리포에서만 사용되는 사내 젬:
- RubyGems에 새 버전을 공개하지 않을 것입니다.
- 이미 발행된 RubyGems에서 버전을 가져오지 않을 것입니다. 응용 프로그램이 해당 버전에 의존할 수 있기 때문입니다.
- 이러한 젬을
gems/
로 이동할 것입니다. - 이러한 젬은
Gemfile
의path:
를 통해 참조될 것입니다.
-
-
모노리포에 판매된 외부
vendor/gems/
:- 아직 업스트림으로 통합되지 않거나 통합되지 않을 수 있는 수정 사항이 필요한 경우 저장소에서 유지할 것입니다.
- 판매될 수 있는 젬은 제3자에 의해 발행될 것으로 예상됩니다.
- 이러한 젬은 우리가 RubyGems에 발행하지 않을 것입니다.
- 이러한 젬은 RubyGems에 의존할 수 없기 때문에
Gemfile
의path:
를 통해 참조될 것입니다.
젬 이름 예약
새로운 젬을 포함하는 공개 코드를 게시하기 전에 젬 이름을 예약하여 RubyGems에서 이름을 차지하지 못하도록 예방 조치로 이름을 예약합니다.
젬 이름을 예약하려면 루비 젬 생성및 발행 단계를 따라 다음 변경 사항을 포함하세요:
- 버전으로
0.0.0
를 사용합니다. - 내용이
raise "Reserved for GitLab"
인lib/NAME.rb
단일 파일을 포함합니다. -
build
와publish
를 수행하고 성공 여부를 확인하기 위해 https://rubygems.org/gems/를 확인합니다.