GitLab 유틸리티

개발을 용이하게하기 위해 다양한 유틸리티를 개발했습니다.

MergeHash

merge_hash.rb를 참조하십시오:

  • 해시 배열을 깊이 Merge합니다:

    Gitlab::Utils::MergeHash.merge(
      [{ hello: ["world"] },
       { hello: "Everyone" },
       { hello: { greetings: ['Bonjour', 'Hello', 'Hallo', 'Dzien dobry'] } },
        "Goodbye", "Hallo"]
    )
    

    결과는 다음과 같습니다:

    [
      {
        hello:
          [
            "world",
            "Everyone",
            { greetings: ['Bonjour', 'Hello', 'Hallo', 'Dzien dobry'] }
          ]
      },
      "Goodbye"
    ]
    
  • 해시에서 모든 키와 값들을 배열로 추출합니다:

    Gitlab::Utils::MergeHash.crush(
      { hello: "world", this: { crushes: ["an entire", "hash"] } }
    )
    

    결과는 다음과 같습니다:

    [:hello, "world", :this, :crushes, "an entire", "hash"]
    

Override

override.rb를 참조하십시오:

  • 이 유틸리티는 한 메소드가 다른 메소드를 재정의하는지 여부를 확인하는 데 도움이 됩니다. Java의 @Override 주석이나 Scala의 override 키워드와 같은 개념입니다. 그러나 ENV['STATIC_VERIFICATION']이 프로덕션 런타임 오버헤드를 피하기 위해 설정된 경우에만 이 확인을 실행합니다. 이 유틸리티는 다음을 확인하는 데 유용합니다:

    • 재정의 메소드에 오타가 있는지 여부.
    • 원래의 재정의 메소드를 무의미하게 만드는 재정의된 메소드의 이름이 변경되었는지 여부.

      간단한 예는 다음과 같습니다:

      class Base
        def execute
        end
      end
          
      class Derived < Base
        extend ::Gitlab::Utils::Override
            
        override :execute # 여기서 재정의 확인이 발생합니다.
        def execute
        end
      end
      

      이는 모듈에도 적용됩니다:

      module Extension
        extend ::Gitlab::Utils::Override
            
        override :execute # 모듈에서는 이게 즉시 확인되지 않습니다.
        def execute
        end
      end
          
      class Derived < Base
        prepend Extension # 여기서 재정의 확인이 발생합니다. 모듈에서는 확인되지 않습니다.
      end
      

      이 확인은 오직 다음 중 하나일 때만 발생합니다:

      • 재정의 메소드가 클래스에 정의된 경우 또는:
      • 재정의 메소드가 모듈에 정의되고 다른 클래스 또는 모듈에 선행되는 경우.

      왜냐하면 클래스나 선행된 모듈만이 실제로 메소드를 재정의할 수 있기 때문입니다. 모듈을 다른 곳에 포함하거나 확장시키는 것은 아무것도 재정의할 수 없습니다.

ActiveSupport::Concern, prepend, 및 class_methods와의 상호작용

ActiveSupport::Concern을 사용하여 클래스 메소드를 포함하는 경우 일반적인 Ruby 모듈과는 달리 예상되는 결과를 얻을 수 없습니다.

Prependable이 이미 ActiveSupport::Concern에 대한 패치로 prepend를 가능케 하였기 때문에, 이것이 overrideclass_methods와 어떻게 상호작용하는지에 영향을 미치게 됩니다. 해결책으로는 Prependable 모듈을 정의할 때 extend를 사용합니다.

이를 통해 우리는 override를 사용하여 위에서 언급한 문맥에서 사용된 class_methods를 검증할 수 있습니다. 이 해결책은 검증할 때만 작동하며, 응용 프로그램 자체를 실행할 때는 작동하지 않습니다.

이 해결책의 효과를 보여주는 예시 코드 블록은 다음과 같습니다:

module Base
  extend ActiveSupport::Concern
  
  class_methods do
    def f
    end
  end
end

module Derived
  include Base
end

# 해결책 없이
Base.f    # => NoMethodError
Derived.f # => nil

# 해결책을 적용한 경우
Base.f    # => nil
Derived.f # => nil

StrongMemoize

strong_memoize.rb를 참조하십시오:

  • nil 또는 false일지라도 값을 메모이제합니다.

    종종 @value ||= compute를 사용합니다. 그러나 compute가 결국 nil을 반환할 수 있으며 다시 계산하고 싶지 않을 때 이는 잘 작동하지 않습니다. 대신에 defined?을 사용하여 값이 설정되었는지 여부를 확인할 수 있습니다. 이러한 패턴을 작성하는 것은 지루하며, StrongMemoize가 이를 사용하는 데 도움이 됩니다.

    다음과 같은 패턴을 작성하는 대신에:

    class Find
      def result
        return @result if defined?(@result)
          
        @result = search
      end
    end
    

    다음과 같이 작성할 수 있습니다:

    class Find
      include Gitlab::Utils::StrongMemoize
        
      def result
        search
      end
      strong_memoize_attr :result
        
      def enabled?
        Feature.enabled?(:some_feature)
      end
      strong_memoize_attr :enabled?
    end
    

    매개변수를 가진 메소드에 strong_memoize_attr를 사용하는 것은 지원되지 않습니다. 또한 override와 결합하면 잘 작동하지 않고 잘못된 결과를 메모이제할 수 있습니다.

    대신에 strong_memoize_with를 사용하십시오.

    # 안 좋은 예
    def expensive_method(arg)
      # ...
    end
    strong_memoize_attr :expensive_method
      
    # 좋은 예
    def expensive_method(arg)
      strong_memoize_with(:expensive_method, arg) do
        # ...
      end
    end
    

    또한, 인수를 사용하는 메소드를 메모이제하는 데 도움을 주는 strong_memoize_with도 있습니다. 이는 매개변수로 가져갈 수 있는 값의 수가 적거나 반복적으로 동일한 인수를 가진 메소드에 사용해야 합니다.

    class Find
      include Gitlab::Utils::StrongMemoize
        
      def result(basic: true)
        strong_memoize_with(:result, basic) do
          search(basic)
        end
      end
    
  • 메모이제를 지우기

    class Find
      include Gitlab::Utils::StrongMemoize
    end
      
    Find.new.clear_memoization(:result)
    

RequestCache

request_cache.rb를 참조하십시오.

이 모듈은 RequestStore에서 값들을 캐시하는 간단한 방법을 제공하며, 캐시 키는 클래스 이름, 메소드 이름, 선택적으로 사용자 정의된 인스턴스 레벨 값들, 선택적으로 사용자 정의된 메소드 레벨 값들, 그리고 선택적으로 메소드 인수에 기반합니다.

인스턴스 레벨 사용자 정의된 값만 사용하는 간단한 예는 다음과 같습니다:

class UserAccess
  extend Gitlab::Cache::RequestCache
  
  request_cache_key do
    [user&.id, project&.id]
  end
  
  request_cache def can_push_to_branch?(ref)
    # ...
  end
end

이렇게 하면 can_push_to_branch?의 결과는 캐시 키를 기반으로 RequestStore.store에 캐싱됩니다. 현재 활성화되어 있지 않다면 해시에 저장되며, 캐시 논리는 동일합니다.

또한 다른 메소드에 대해 다른 전략을 설정할 수 있습니다:

class Commit
  extend Gitlab::Cache::RequestCache
  
  def author
    User.find_by_any_email(author_email)
  end
  request_cache(:author) { author_email }
end

ReactiveCaching

ReactiveCaching에 대한 문서를 읽어보세요.

CircuitBreaker

Gitlab::CircuitBreaker는 회로 차단기 보호로 코드를 실행해야 하는 모든 클래스 주변에 래핑할 수 있습니다. 이는 회로 차단기 기능을 갖춘 run_with_circuit 메서드를 제공하며, 단순한 실패를 방지하고 시스템 내구성을 향상시킵니다. 회로 차단기 패턴에 대한 자세한 정보는 아래를 참조하세요:

회로 차단기 사용

회로 차단기 래퍼를 사용하려면:

class MyService
  def call_external_service
    Gitlab::CircuitBreaker.run_with_circuit('ServiceName') do
      # 외부 서비스와 상호 작용하는 코드가 이곳에 들어갑니다
      
      raise Gitlab::CircuitBreaker::InternalServerError # 문제가 발생하면 예외를 발생시킵니다
    end
  end
end

call_external_service 메서드는 외부 서비스와 상호 작용하는 예시입니다. 외부 서비스와 상호 작용하는 코드를 run_with_circuit으로 감싸면 메서드가 회로 차단기 내에서 실행됩니다.

이 메서드는 코드 블록 실행 중에 발생한 InternalServerError 오류를 일정 수준의 오류 임계값으로 취급합니다. 회로 차단기는 오류 수와 요청률을 추적하며, 구성된 오류 임계값 또는 볼륨 임계값에 도달하면 회로를 엽니다. 회로가 열리면 후속 요청은 코드 블록을 실행하지 않고 빠르게 실패하며, 회로 차단기는 일정 시간 이내에 서비스의 가용성을 테스트하기 위해 일부 요청을 주기적으로 허용한 뒤 다시 회로를 닫습니다.

구성

사용하는 고유한 회로마다 서비스 이름을 지정해야 합니다. 이것은 회로를 식별하는 CamelCase 문자열이어야 합니다.

예를 들어, 회로마다 오버라이드할 수 있는 기본값이 있습니다:

Gitlab::CircuitBreaker.run_with_circuit('ServiceName', options = { volume_threshold: 5 }) do
  ...
end

기본값은 다음과 같습니다:

  • exceptions: [Gitlab::CircuitBreaker::InternalServerError]
  • error_threshold: 50
  • volume_threshold: 10
  • sleep_window: 90
  • time_window: 60