The Codest
  • Sobre nós
  • Serviços
    • Desenvolvimento de software
      • Desenvolvimento de front-end
      • Desenvolvimento backend
    • Staff Augmentation
      • Programadores Frontend
      • Programadores de back-end
      • Engenheiros de dados
      • Engenheiros de nuvem
      • Engenheiros de GQ
      • Outros
    • Aconselhamento
      • Auditoria e consultoria
  • Indústrias
    • Fintech e Banca
    • E-commerce
    • Adtech
    • Tecnologia da saúde
    • Fabrico
    • Logística
    • Automóvel
    • IOT
  • Valor para
    • CEO
    • CTO
    • Gestor de entregas
  • A nossa equipa
  • Case Studies
  • Saber como
    • Blogue
    • Encontros
    • Webinars
    • Recursos
Carreiras Entrar em contacto
  • Sobre nós
  • Serviços
    • Desenvolvimento de software
      • Desenvolvimento de front-end
      • Desenvolvimento backend
    • Staff Augmentation
      • Programadores Frontend
      • Programadores de back-end
      • Engenheiros de dados
      • Engenheiros de nuvem
      • Engenheiros de GQ
      • Outros
    • Aconselhamento
      • Auditoria e consultoria
  • Valor para
    • CEO
    • CTO
    • Gestor de entregas
  • A nossa equipa
  • Case Studies
  • Saber como
    • Blogue
    • Encontros
    • Webinars
    • Recursos
Carreiras Entrar em contacto
Seta para trás VOLTAR
2019-03-08
Desenvolvimento de software

Otimizar o código com Query Objects

The Codest

Tomasz Szkaradek

Arquiteto de desenvolvimento

É muito provável que no trabalho já se tenha deparado várias vezes com modelos sobrecarregados e um grande número de chamadas nos controladores. Baseando-me no conhecimento do ambiente Rails, neste artigo, vou propor uma solução simples para este problema.

Um aspeto muito importante do carris é minimizar o número de dependências redundantes, e é por isso que todo o ambiente Rails tem promovido recentemente a abordagem de objeto de serviço e o uso do PORO (Pure Old Rubi Object). Uma descrição de como utilizar esta solução pode ser encontrada em aqui. Neste artigo, vamos resolver o conceito passo a passo e adaptá-lo ao problema.

Problema

Numa aplicação hipotética, estamos a lidar com um sistema de transacções complicado. O nosso modelo, que representa cada transação, tem um conjunto de âmbitos que o ajudam a obter dados. É uma excelente forma de facilitar o trabalho, uma vez que os dados podem ser encontrados num único local. No entanto, isto não dura muito tempo. Com o desenvolvimento da aplicação, o projeto está a tornar-se cada vez mais complicado. Os âmbitos de aplicação já não têm referências "onde" simples, faltam-nos dados e começamos a carregar relações. Ao fim de algum tempo, faz lembrar um complicado sistema de espelhos. E, o que é pior, não sabemos como fazer um lambda com várias linhas!

Abaixo, encontrará um modelo de aplicação já expandido. As transacções do sistema de pagamento são armazenadas. Como pode ver no exemplo abaixo:

class Transação  { where(visible: true) }

  âmbito(:ativo, lambda do
    junta(<<-SQL
      LEFT OUTER JOIN source ON transactions.source_id = source.id
      AND source.accepted_at IS NOT NULL
    SQL
  end)
end

O modelo é uma coisa, mas à medida que a escala do nosso projeto aumenta, os controladores também começam a inchar. Vejamos o exemplo abaixo:

classe TransactionsController < ApplicationController
  def index
    @transactions = Transaction.for_publishers
                                   .ativo
                                   .visível
                                   .joins("LEFT JOIN withdrawal_items ON withdrawal_items.transaction_id = transactions.id")
                                   .joins("LEFT JOIN withdrawals ON withdrawals.id = withdrawal_items.withdrawal_id OR
 (withdrawals.id = source.resource_id AND source.resource_type = 'Withdrawal')")
                                   .order(:created_at)
                                   .page(params[:page])
                                   .per(params[:page])
    @transacções = apply_filters(@transacções)
  fim
fim

Aqui podemos ver muitas linhas de métodos encadeados juntamente com junções adicionais que não queremos efetuar em muitos sítios, apenas neste em particular. Os dados anexados são usados posteriormente pelo método apply_filters, que adiciona a filtragem de dados apropriada, com base nos parâmetros GET. Claro que podemos transferir algumas destas referências para o âmbito, mas não é este o problema que estamos a tentar resolver?

Solução

Uma vez que já sabemos que temos um problema, temos de o resolver. Com base na referência da introdução, utilizaremos aqui a abordagem PORO. Neste caso concreto, esta abordagem é designada por objeto de consulta, que é um desenvolvimento do conceito de objectos de serviço.

Vamos criar um novo diretório chamado "services", localizado no diretório apps do nosso projeto. Lá, criaremos uma classe chamada Consulta de transacções.

classe TransactionsQuery
fim

Como próximo passo, precisamos de criar um inicializador onde será criado um caminho de chamada predefinido para o nosso objeto

classe TransactionsQuery
  def initialize(scope = Transaction.all)
    @scope = scope
  end
fim

Graças a isto, poderemos transferir a relação do registo ativo para a nossa instalação. Agora podemos transferir todos os nossos âmbitos para a classe, que só são necessários no controlador apresentado.

classe TransactionsQuery
  def initialize(scope = Transaction.all)
    @scope = scope
  fim

  private

  def active(scope)
    scope.joins(<<-SQL
      LEFT OUTER JOIN 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("transacções.*")
         .junta(:conta)
         .where("accounts.owner_type = 'Publisher'")
         .junta("JOIN publishers ON owner_id = publishers.id")
  fim
fim

Ainda nos falta a parte mais importante, ou seja, reunir os dados numa string e tornar a interface pública. O método em que juntaremos tudo será designado por "call".

O que é realmente importante é que utilizaremos a variável de instância @scope, onde se encontra o âmbito da nossa chamada.

classe TransactionsQuery
  ...
  def call
    visível(@escopo)
        .then(&method(:active))
        .then(&method(:for_publishers))
        .order(:created_at)
  fim

  privado
  ...
end

Toda a classe se apresenta como o seguinte:

classe TransactionsQuery
  def initialize(scope = Transaction.all)
    @scope = scope
  fim

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

  privado

  def active(scope)
    scope.joins(<<-SQL
      LEFT OUTER JOIN 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("transacções.*")
         .junta(:conta)
         .where("accounts.owner_type = 'Publisher'")
         .junta("JOIN publishers ON owner_id = publishers.id")
  fim
fim

Após a nossa limpeza, o modelo parece definitivamente mais leve. Neste caso, concentramo-nos apenas na validação dos dados e nas relações entre outros modelos.

class Transação < ActiveRecord::Base
  pertence a :conta
  has_one :withdrawal_item
fim

O controlador já implementou a nossa solução; transferimos todas as consultas adicionais para uma classe separada. No entanto, as chamadas, que não tínhamos no modelo, continuam a ser uma questão por resolver. Após algumas alterações, a nossa ação de indexação tem o seguinte aspeto:

classe TransactionsController < ApplicationController
  def index
    @transactions = TransactionsQuery.new
                                     .call
                                     .joins("LEFT JOIN withdrawal_items ON withdrawal_items.accounting_event_id = transactions.id")
                                     .joins("LEFT JOIN retiradas ON retiradas.id = retiradas_itens.retirada_id OR
 (withdrawals.id = source.resource_id AND source.resource_type = 'Withdrawal')")
                                     .order(:created_at)
                                     .page(params[:page])
                                     .per(params[:page])
    @transacções = apply_filters(@transacções)
  fim
fim

Solução

No caso da implementação de boas práticas e convenções, uma boa ideia pode ser substituir todas as ocorrências semelhantes de um determinado problema. Por isso, vamos mover a consulta SQL da ação de índice para o objeto de consulta separado. Chamaremos a isto um TransactionsFilterableQuery aula. O estilo em que preparamos a aula será semelhante ao apresentado em Consulta de transacções. No âmbito do código será introduzido um registo mais intuitivo das grandes consultas SQL, utilizando cadeias de caracteres de várias linhas denominadas heredoc. A solução disponível encontra-se abaixo:

classe TransactionsFilterableQuery
  def initialize(scope = Transaction.all)
    @scope = scope
  fim

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

  privado

  def withdrawal(scope)
    scope.joins(<<-SQL
      LEFT JOIN retiradas ON retiradas.id = itens_retirada.withdrawal_id OR
      (withdrawals.id = source.resource_id AND source.resource_type = 'Withdrawal')
    SQL
  final

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

No caso de alterações no controlador, reduzimos a massa de linhas adicionando o objeto de consulta. É importante que separemos tudo, exceto a parte responsável pela paginação.

classe TransactionsController < ApplicationController
  def index
    @transactions = TransactionsQuery.new.call.then do |scope|
      TransactionsFilterableQuery.new(scope).call
    end.page(params[:page]).per(params[:page])

    @transactions = apply_filters(@transactions)
  fim
fim

Resumo

O objeto de consulta muda muito a abordagem à escrita de consultas SQL. No ActiveRecord, é muito fácil colocar toda a lógica comercial e de base de dados no modelo, uma vez que tudo se encontra num único local. Isto funciona muito bem para aplicações mais pequenas. À medida que a complexidade do projeto aumenta, colocamos a lógica noutros locais. O mesmo objeto de consulta permite-lhe agrupar consultas de membros num problema específico.

Graças a isto, temos uma possibilidade fácil de herança posterior do código e, devido à tipagem de patos, também é possível utilizar estas soluções noutros modelos. A desvantagem desta solução é uma maior quantidade de código e a fragmentação da responsabilidade. No entanto, o facto de querermos ou não aceitar este desafio depende de nós e o quanto somos perturbados por modelos gordos.

Artigos relacionados

Ilustração de uma aplicação de cuidados de saúde para smartphone com um ícone de coração e um gráfico de saúde em ascensão, com o logótipo The Codest, representando soluções digitais de saúde e HealthTech.
Desenvolvimento de software

Softwares para o setor de saúde: Tipos, casos de uso

As ferramentas em que as organizações de cuidados de saúde confiam atualmente não se assemelham em nada às fichas de papel de há décadas atrás. O software de cuidados de saúde apoia agora os sistemas de saúde, os cuidados aos doentes e a prestação de cuidados de saúde modernos em...

OCODEST
Ilustração abstrata de um gráfico de barras em declínio com uma seta ascendente e uma moeda de ouro que simboliza a eficiência ou a poupança de custos. O logótipo The Codest aparece no canto superior esquerdo com o slogan "In Code We Trust" sobre um fundo cinzento claro
Desenvolvimento de software

Como dimensionar a sua equipa de desenvolvimento sem perder a qualidade do produto

Aumentar a sua equipa de desenvolvimento? Saiba como crescer sem sacrificar a qualidade do produto. Este guia cobre sinais de que é hora de escalar, estrutura da equipe, contratação, liderança e ferramentas - além de como o The Codest pode...

OCODEST
Desenvolvimento de software

Construir aplicações Web preparadas para o futuro: ideias da equipa de especialistas do The Codest

Descubra como o The Codest se destaca na criação de aplicações web escaláveis e interactivas com tecnologias de ponta, proporcionando experiências de utilizador perfeitas em todas as plataformas. Saiba como a nossa experiência impulsiona a transformação digital e o negócio...

OCODEST
Desenvolvimento de software

As 10 principais empresas de desenvolvimento de software sediadas na Letónia

Saiba mais sobre as principais empresas de desenvolvimento de software da Letónia e as suas soluções inovadoras no nosso último artigo. Descubra como estes líderes tecnológicos podem ajudar a elevar o seu negócio.

thecodest
Soluções para empresas e escalas

Fundamentos do desenvolvimento de software Java: Um Guia para Terceirizar com Sucesso

Explore este guia essencial sobre o desenvolvimento de software Java outsourcing com sucesso para aumentar a eficiência, aceder a conhecimentos especializados e impulsionar o sucesso do projeto com The Codest.

thecodest

Subscreva a nossa base de conhecimentos e mantenha-se atualizado sobre os conhecimentos do sector das TI.

    Sobre nós

    The Codest - Empresa internacional de desenvolvimento de software com centros tecnológicos na Polónia.

    Reino Unido - Sede

    • Office 303B, 182-184 High Street North E6 2JA
      Londres, Inglaterra

    Polónia - Pólos tecnológicos locais

    • Parque de escritórios Fabryczna, Aleja
      Pokoju 18, 31-564 Cracóvia
    • Embaixada do Cérebro, Konstruktorska
      11, 02-673 Varsóvia, Polónia

      The Codest

    • Início
    • Sobre nós
    • Serviços
    • Case Studies
    • Saber como
    • Carreiras
    • Dicionário

      Serviços

    • Aconselhamento
    • Desenvolvimento de software
    • Desenvolvimento backend
    • Desenvolvimento de front-end
    • Staff Augmentation
    • Programadores de back-end
    • Engenheiros de nuvem
    • Engenheiros de dados
    • Outros
    • Engenheiros de GQ

      Recursos

    • Factos e mitos sobre a cooperação com um parceiro externo de desenvolvimento de software
    • Dos EUA para a Europa: Porque é que as empresas americanas decidem mudar-se para a Europa?
    • Comparação dos centros de desenvolvimento da Tech Offshore: Tech Offshore Europa (Polónia), ASEAN (Filipinas), Eurásia (Turquia)
    • Quais são os principais desafios dos CTOs e dos CIOs?
    • The Codest
    • The Codest
    • The Codest
    • Privacy policy
    • Website terms of use

    Direitos de autor © 2026 por The Codest. Todos os direitos reservados.

    pt_PTPortuguese
    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 cs_CZCzech pt_PTPortuguese