window.pipedriveLeadboosterConfig = { base: 'leadbooster-chat.pipedrive.com', companyId: 11580370, playbookUuid: '22236db1-6d50-40c4-b48f-8b11262155be', version: 2, } ;(function () { var w = window if (w.LeadBooster) { console.warn('LeadBooster가 이미 존재합니다') } else { w.LeadBooster = { q: [], on: 함수 (n, h) { this.q.push({ t: 'o', n: n, h: h }) }, trigger: 함수 (n) { this.q.push({ t: 't', n: n }) }, } } })() 쿼리 개체를 사용한 코드 최적화 - The Codest
The Codest
  • 회사 소개
  • 서비스
    • 소프트웨어 개발
      • 프론트엔드 개발
      • 백엔드 개발
    • Staff Augmentation
      • 프론트엔드 개발자
      • 백엔드 개발자
      • 데이터 엔지니어
      • 클라우드 엔지니어
      • QA 엔지니어
      • 기타
    • IT 자문
      • 감사 및 컨설팅
  • 산업 분야
    • 핀테크 및 뱅킹
    • E-commerce
    • 애드테크
    • 헬스 테크
    • 제조
    • 물류
    • 자동차
    • IOT
  • 가치
    • CEO
    • CTO
    • 배달 관리자
  • 우리 팀
  • Case Studies
  • 방법 알아보기
    • 블로그
    • 모임
    • 웹 세미나
    • 리소스
채용 정보 연락하기
  • 회사 소개
  • 서비스
    • 소프트웨어 개발
      • 프론트엔드 개발
      • 백엔드 개발
    • Staff Augmentation
      • 프론트엔드 개발자
      • 백엔드 개발자
      • 데이터 엔지니어
      • 클라우드 엔지니어
      • QA 엔지니어
      • 기타
    • IT 자문
      • 감사 및 컨설팅
  • 가치
    • CEO
    • CTO
    • 배달 관리자
  • 우리 팀
  • Case Studies
  • 방법 알아보기
    • 블로그
    • 모임
    • 웹 세미나
    • 리소스
채용 정보 연락하기
뒤로 화살표 뒤로 가기
2019-03-08
소프트웨어 개발

쿼리 개체를 사용한 코드 최적화

The Codest

토마스 스카라덱

개발 아키텍트

직장에서 과부하 모델과 컨트롤러의 엄청난 수의 호출을 꽤 많이 접했을 가능성이 큽니다. 이 글에서는 Rails 환경에 대한 지식을 바탕으로 이 문제에 대한 간단한 해결책을 제안하려고 합니다.

레일즈 애플리케이션에서 매우 중요한 측면은 중복 종속성의 수를 최소화하는 것이므로, 최근 전체 레일즈 환경에서 서비스 객체 접근 방식과 PORO(Pure Old Ruby Object) 방법의 사용을 장려하고 있는 이유입니다. 이러한 솔루션을 사용하는 방법에 대한 설명은 다음과 같습니다. 여기. 이 기사에서는 개념을 단계별로 해결하고 문제에 맞게 조정할 것입니다.

문제

가상의 애플리케이션에서 우리는 복잡한 트랜잭션 시스템을 다루고 있습니다. 각 트랜잭션을 나타내는 저희 모델에는 데이터를 가져오는 데 도움이 되는 일련의 범위가 있습니다. 한 곳에서 찾을 수 있기 때문에 업무 효율성이 뛰어납니다. 그러나 이것은 오래 가지 않습니다. 애플리케이션의 개발과 함께 프로젝트 는 점점 더 복잡해지고 있습니다. 범위에는 더 이상 단순한 '위치' 참조가 없고, 데이터가 부족하며, 관계를 로드하기 시작합니다. 잠시 후, 복잡한 거울 시스템을 떠올리게 합니다. 그리고 더 나쁜 것은 여러 줄의 람다를 수행하는 방법을 모른다는 것입니다!

아래에서 이미 확장된 애플리케이션 모델을 확인할 수 있습니다. 결제 시스템 트랜잭션이 저장됩니다. 아래 예시에서 볼 수 있듯이:

트랜잭션 클래스  { where(visible: true) }

  scope(:active, 람다 do
    joins(<<-SQL
      좌측 외부 조인 source ON transactions.source_id = source.id
      AND source.accepted_at IS NOT NULL
    SQL
  end)
end

모델도 문제지만 프로젝트의 규모가 커지면 컨트롤러도 늘어나기 시작합니다. 아래 예시를 살펴보겠습니다:

트랜잭션 컨트롤러 클래스 < 애플리케이션 컨트롤러
  def index
    트랜잭션 = 트랜잭션.for_publishers
                                   .active
                                   .visible
                                   .joins("LEFT JOIN withdrawal_items ON withdrawal_items.transaction_id = transactions.id")
                                   .joins("LEFT JOIN 출금 ON 출금.id = 출금_항목.출금_id 또는
 (withdrawals.id = source.resource_id AND source.resource_type = 'Withdrawal')")
                                   .order(:created_at)
                                   .page(params[:page])
                                   .per(params[:page])
    트랜잭션 = 적용_필터(@트랜잭션)
  end
end

여기에서는 여러 곳에서 수행하지 않으려는 추가 조인과 함께 여러 줄의 연결된 메서드를 볼 수 있지만, 이 특정 메서드에서만 볼 수 있습니다. 첨부된 데이터는 나중에 GET 매개 변수를 기반으로 적절한 데이터 필터링을 추가하는 apply_filters 메서드에서 사용됩니다. 물론 이러한 참조 중 일부를 범위로 전송할 수 있지만, 이것이 우리가 실제로 해결하려고 하는 문제가 아닐까요?

솔루션

우리는 이미 우리가 가진 문제에 대해 알고 있으므로 이 문제를 해결해야 합니다. 서론의 참조를 바탕으로 여기서는 PORO 접근 방식을 사용하겠습니다. 이 경우 이 접근 방식을 쿼리 객체라고 하며, 이는 서비스 객체 개념을 발전시킨 것입니다.

프로젝트의 앱 디렉터리에 "services"라는 새 디렉터리를 만들어 보겠습니다. 여기에 다음과 같은 이름의 클래스를 생성합니다. 트랜잭션 쿼리.

트랜잭션 쿼리 클래스
끝

다음 단계로, 객체의 기본 호출 경로가 생성될 이니셜라이저를 만들어야 합니다.

트랜잭션 쿼리 클래스
  def initialize(scope = Transaction.all)
    범위 = 범위
  end
end

덕분에 활성 레코드에서 우리 시설로 관계를 전송할 수 있습니다. 이제 제시된 컨트롤러에만 필요한 모든 범위를 클래스로 전송할 수 있습니다.

트랜잭션 쿼리 클래스
  def initialize(scope = Transaction.all)
    범위 = 범위
  end

  private

  def active(scope)
    scope.joins(<<-SQL
      좌측 외부 조인 source ON transactions.source_id = source.id
      AND source.accepted_at IS NOT NULL
    SQL
  end

  def visible(scope)
    scope.where(visible: true)
  end

  def for_publishers(scope)
    scope.select("transactions.*")
         .joins(:account)
         .where("accounts.owner_type = '퍼블리셔'")
         .joins("JOIN publishers ON owner_id = publishers.id")
  end
end

우리는 여전히 가장 중요한 부분, 즉 데이터를 하나의 문자열로 수집하고 인터페이스를 공개하는 것을 놓치고 있습니다. 모든 것을 하나로 묶는 메서드를 "호출"이라고 이름 붙이겠습니다.

정말 중요한 것은 호출의 범위가 있는 @scope 인스턴스 변수를 사용한다는 것입니다.

트랜잭션 쿼리 클래스
  ...
  def call
    visible(@scope)
        .then(&method(:active))
        .then(&method(:for_publishers))
        .order(:created_at)
  end

  private
  ...
end

전체 수업은 다음과 같이 구성됩니다:

트랜잭션 쿼리 클래스
  def initialize(scope = Transaction.all)
    범위 = 범위
  end

  def call
    visible(@scope)
        .then(&method(:active))
        .then(&method(:for_publishers))
        .order(:created_at)
  end

  private

  def active(scope)
    scope.joins(<<-SQL
      좌측 외부 조인 source ON transactions.source_id = source.id
      AND source.accepted_at IS NOT NULL
    SQL
  end

  def visible(scope)
    scope.where(visible: true)
  end

  def for_publishers(scope)
    scope.select("transactions.*")
         .joins(:account)
         .where("accounts.owner_type = '퍼블리셔'")
         .joins("JOIN publishers ON owner_id = publishers.id")
  end
end

정리가 끝나면 모델이 확실히 가벼워 보입니다. 이제 데이터 유효성 검사와 다른 모델 간의 관계에만 집중할 수 있습니다.

트랜잭션 클래스 < 액티브 레코드::베이스
  belongs_to :계정
  has_one :withdrawal_item
end

컨트롤러는 이미 우리의 솔루션을 구현했으며 모든 추가 쿼리를 별도의 클래스로 옮겼습니다. 그러나 모델에 없었던 호출은 여전히 해결되지 않은 문제로 남아 있습니다. 몇 가지 변경 후 인덱스 작업은 다음과 같습니다:

트랜잭션 컨트롤러 클래스 < 애플리케이션 컨트롤러
  def index
    트랜잭션 = 트랜잭션 쿼리.new
                                     .call
                                     .joins("LEFT JOIN withdrawal_items ON withdrawal_items.accounting_event_id = transactions.id")
                                     .joins("LEFT JOIN 출금 ON 출금.id = 출금_항목.출금_id 또는
 (withdrawals.id = source.resource_id AND source.resource_type = 'Withdrawal')")
                                     .order(:created_at)
                                     .page(params[:page])
                                     .per(params[:page])
    트랜잭션 = 적용_필터(@트랜잭션)
  end
end

솔루션

모범 사례와 규칙을 구현하는 경우, 주어진 문제의 유사한 발생을 모두 대체하는 것이 좋은 아이디어일 수 있습니다. 따라서 SQL 쿼리를 인덱스 작업에서 별도의 쿼리 개체로 옮깁니다. 이것을 트랜잭션 필터링 가능 쿼리 클래스입니다. 수업을 준비하는 스타일은 다음과 유사합니다. 트랜잭션 쿼리. 의 일환으로 코드 가 변경되면 여러 줄의 문자열을 사용하여 보다 직관적인 대용량 SQL 쿼리 레코드가 밀수됩니다. heredoc. 사용 가능한 솔루션은 아래에서 확인할 수 있습니다:

트랜잭션 필터링 쿼리 클래스
  def initialize(scope = Transaction.all)
    범위 = 범위
  end

  def call
    withdrawal(@scope).then(&method(:withdrawal_items))
  end

  private

  def withdrawal(scope)
    scope.joins(<<-SQL
      LEFT JOIN withdrawals ON withdrawals.id = withdrawal_items.withdrawal_id OR
      (withdrawals.id = source.resource_id AND source.resource_type = '출금')
    SQL
  end

  def withdrawal_items(scope)
    scope.joins(<<-SQL
      LEFT JOIN withdrawal_items ON withdrawal_items.accounting_event_id = transactions.id
    SQL
  end
end

컨트롤러가 변경된 경우 쿼리 객체를 추가하여 줄의 질량을 줄입니다. 페이지 매김을 담당하는 부분을 제외한 모든 부분을 분리하는 것이 중요합니다.

트랜잭션 컨트롤러 클래스 < 애플리케이션 컨트롤러
  def index
    트랜잭션 = 트랜잭션 쿼리.new.call.then do |scope|
      트랜잭션 필터링 쿼리.new(scope).call
    end.page(params[:page]).per(params[:page])

    트랜잭션 = 적용_필터(@트랜잭션)
  end
end

요약

쿼리 객체는 SQL 쿼리 작성 방식에서 많은 변화를 가져옵니다. ActiveRecord에서는 모든 것이 한 곳에 있기 때문에 모든 비즈니스 및 데이터베이스 로직을 모델에 배치하기가 매우 쉽습니다. 이는 소규모 애플리케이션에 매우 효과적입니다. 프로젝트의 복잡성이 증가함에 따라 로직을 다른 위치에 설정합니다. 동일한 쿼리 객체를 사용하면 멤버 쿼리 쿼리를 특정 문제로 그룹화할 수 있습니다.

덕분에 나중에 코드를 쉽게 상속할 수 있으며 덕 타이핑 덕분에 다른 모델에서도 이러한 솔루션을 사용할 수 있습니다. 이 솔루션의 단점은 코드의 양이 많아지고 책임 소재가 세분화된다는 점입니다. 그러나 이러한 도전을 받아들일지 여부는 우리에게 달려 있으며 뚱뚱한 모델에 의해 얼마나 심하게 방해를 받느냐에 달려 있습니다.

관련 문서

소프트웨어 개발

미래 지향적인 웹 앱 구축: The Codest의 전문가 팀이 제공하는 인사이트

The Codest가 최첨단 기술로 확장 가능한 대화형 웹 애플리케이션을 제작하고 모든 플랫폼에서 원활한 사용자 경험을 제공하는 데 탁월한 성능을 발휘하는 방법을 알아보세요. Adobe의 전문성이 어떻게 디지털 혁신과 비즈니스를 촉진하는지 알아보세요...

최신
소프트웨어 개발

라트비아에 본사를 둔 10대 소프트웨어 개발 기업

최신 기사에서 라트비아 최고의 소프트웨어 개발 기업과 그들의 혁신적인 솔루션에 대해 알아보세요. 이러한 기술 리더들이 어떻게 귀사의 비즈니스를 향상시키는 데 도움을 줄 수 있는지 알아보세요.

thecodest
엔터프라이즈 및 스케일업 솔루션

Java 소프트웨어 개발 필수 사항: 성공적인 아웃소싱을 위한 가이드

The Codest로 효율성을 높이고 전문 지식을 활용하며 프로젝트 성공을 이끌 수 있는 성공적인 outsourcing Java 소프트웨어 개발에 대한 이 필수 가이드를 살펴보세요.

thecodest
소프트웨어 개발

폴란드 아웃소싱을 위한 최고의 가이드

폴란드에서 outsourcing가 급증한 것은 경제, 교육, 기술 발전으로 인한 IT 성장과 비즈니스 친화적인 환경이 조성된 덕분입니다.

더코데스트
엔터프라이즈 및 스케일업 솔루션

IT 감사 도구 및 기술에 대한 완벽한 가이드

IT 감사는 안전하고 효율적이며 규정을 준수하는 시스템을 보장합니다. 전체 기사를 읽고 그 중요성에 대해 자세히 알아보세요.

The Codest
야쿱 야쿠보비치 CTO & 공동 설립자

지식창고를 구독하고 IT 분야의 전문 지식을 최신 상태로 유지하세요.

    회사 소개

    The Codest - 폴란드에 기술 허브를 둔 국제 소프트웨어 개발 회사입니다.

    영국 - 본사

    • 사무실 303B, 182-184 하이 스트리트 노스 E6 2JA
      영국 런던

    폴란드 - 현지 기술 허브

    • 파브리츠나 오피스 파크, 알레야
      포코주 18, 31-564 크라쿠프
    • 뇌 대사관, 콘스트럭터스카
      11, 02-673 바르샤바, 폴란드

      The Codest

    • 홈
    • 회사 소개
    • 서비스
    • Case Studies
    • 방법 알아보기
    • 채용 정보
    • 사전

      서비스

    • IT 자문
    • 소프트웨어 개발
    • 백엔드 개발
    • 프론트엔드 개발
    • Staff Augmentation
    • 백엔드 개발자
    • 클라우드 엔지니어
    • 데이터 엔지니어
    • 기타
    • QA 엔지니어

      리소스

    • 외부 소프트웨어 개발 파트너와의 협력에 대한 사실과 오해
    • 미국에서 유럽으로: 미국 스타트업이 유럽으로 이전을 결정하는 이유
    • 테크 오프쇼어 개발 허브 비교: 테크 오프쇼어 유럽(폴란드), 아세안(필리핀), 유라시아(터키)
    • CTO와 CIO의 주요 과제는 무엇인가요?
    • The Codest
    • The Codest
    • The Codest
    • Privacy policy
    • 웹사이트 이용 약관

    저작권 © 2025 by The Codest. 모든 권리 보유.

    ko_KRKorean
    en_USEnglish de_DEGerman sv_SESwedish da_DKDanish nb_NONorwegian fiFinnish fr_FRFrench pl_PLPolish arArabic it_ITItalian jaJapanese es_ESSpanish nl_NLDutch etEstonian elGreek ko_KRKorean