# Como Usar ActiveJob para Processar Consultas de CPF em Segundo Plano no Rails

> Aprenda a usar ActiveJob no Rails para processar consultas de CPF em segundo plano, com Sidekiq, retry automático e callbacks de conclusão.

**Publicado:** 05/08/2024
**Autor:** Redação CPFHub.io
**URL:** https://cpfhub.io/blog/activejob-processar-consultas-cpf-segundo-plano-rails

---


Para processar consultas de CPF em segundo plano no Rails sem bloquear requisições HTTP, crie um `ConsultarCpfJob` com ActiveJob e Sidekiq: o controller responde imediatamente com status 202, o job consulta a API da CPFHub.io em background com retry automático e persiste o resultado no banco — suportando desde validações individuais no checkout até lotes de milhares de CPFs com filas priorizadas.

## Introdução

Em aplicações Rails, processar consultas de CPF de forma síncrona durante uma requisição HTTP pode causar timeouts e degradar a experiência do usuário. O ActiveJob, framework nativo do Rails para processamento em background, oferece uma abstração elegante que funciona com diversos backends como Sidekiq, Resque e Delayed Job.

## Configuração do ActiveJob com Sidekiq

Primeiro, configure o Rails para utilizar o Sidekiq como backend do ActiveJob.

```ruby
# Gemfile
gem "sidekiq", "~> 7.0"
gem "faraday", "~> 2.0"
gem "faraday-retry", "~> 2.0"

# config/application.rb
config.active_job.queue_adapter = :sidekiq

# config/sidekiq.yml
:concurrency: 10
:queues:
 - [consulta_cpf_urgente, 3]
 - [consulta_cpf_padrao, 2]
 - [consulta_cpf_lote, 1]
```

| Fila | Prioridade | Uso |
|---|---|---|
| consulta_cpf_urgente | Alta (3) | Checkout em tempo real |
| consulta_cpf_padrao | Média (2) | Cadastros e validações regulares |
| consulta_cpf_lote | Baixa (1) | Processamento em massa |

---

## Criando o job de consulta de CPF

O job encapsula toda a lógica de consulta à API, tratamento de erros e persistência do resultado.

```ruby
# app/jobs/consultar_cpf_job.rb
class ConsultarCpfJob < ApplicationJob
 queue_as :consulta_cpf_padrao

 retry_on Faraday::TimeoutError, wait: :exponentially_longer, attempts: 5
 retry_on Faraday::ConnectionFailed, wait: 30.seconds, attempts: 3
 discard_on ActiveJob::DeserializationError

 before_perform :registrar_inicio
 after_perform :registrar_conclusao

 def perform(cpf, callback_class: nil, callback_id: nil)
 cliente_api = criar_cliente_api
 resposta = cliente_api.get("/cpf/#{cpf}")

 resultado = JSON.parse(resposta.body)

 if resultado["success"]
 salvar_resultado(cpf, resultado["data"])
 executar_callback(callback_class, callback_id, resultado["data"])
 else
 registrar_falha(cpf, "CPF nao encontrado na base")
 end
 rescue Faraday::ClientError => e
 registrar_falha(cpf, "Erro na API: #{e.message}")
 raise
 end

 private

 def criar_cliente_api
 Faraday.new(url: "https://api.cpfhub.io") do |conn|
 conn.request :retry, max: 2, interval: 1, backoff_factor: 2
 conn.headers["x-api-key"] = ENV["CPFHUB_API_KEY"]
 conn.headers["Content-Type"] = "application/json"
 conn.options.timeout = 10
 conn.options.open_timeout = 5
 conn.adapter Faraday.default_adapter
 end
 end

 def salvar_resultado(cpf, dados)
 ConsultaCpf.create!(
 cpf: cpf,
 nome: dados["name"],
 genero: dados["gender"],
 data_nascimento: dados["birthDate"],
 status: "sucesso",
 consultado_em: Time.current
 )
 end

 def executar_callback(callback_class, callback_id, dados)
 return unless callback_class && callback_id

 klass = callback_class.constantize
 registro = klass.find(callback_id)
 registro.cpf_validado!(dados)
 end

 def registrar_inicio
 Rails.logger.info("[ConsultarCpfJob] Iniciando consulta")
 end

 def registrar_conclusao
 Rails.logger.info("[ConsultarCpfJob] Consulta concluida")
 end

 def registrar_falha(cpf, motivo)
 ConsultaCpf.create!(
 cpf: cpf,
 status: "falha",
 motivo_falha: motivo,
 consultado_em: Time.current
 )
 end
end
```

---

## Enfileirando jobs a partir de controllers

O controller recebe a requisição e enfileira o job, respondendo imediatamente ao cliente.

```ruby
# app/controllers/api/cpf_controller.rb
module Api
 class CpfController < ApplicationController
 def consultar
 cpf = params[:cpf].gsub(/\D/, "")

 unless cpf_valido?(cpf)
 render json: { erro: "CPF invalido" }, status: :unprocessable_entity
 return
 end

 # Verificar se já existe no cache
 consulta_existente = ConsultaCpf.recente.find_by(cpf: cpf)
 if consulta_existente&.sucesso?
 render json: {
 fonte: "cache",
 dados: consulta_existente.as_json
 }
 return
 end

 # Enfileirar para processamento em background
 job = ConsultarCpfJob.perform_later(cpf)

 render json: {
 mensagem: "Consulta enfileirada com sucesso",
 job_id: job.provider_job_id,
 status: "pendente"
 }, status: :accepted
 end

 def consultar_lote
 cpfs = params[:cpfs]

 cpfs.each_with_index do |cpf, index|
 ConsultarCpfJob
 .set(queue: :consulta_cpf_lote, wait: index.seconds)
 .perform_later(cpf.gsub(/\D/, ""))
 end

 render json: {
 mensagem: "#{cpfs.size} CPFs enfileirados para processamento",
 status: "pendente"
 }, status: :accepted
 end

 private

 def cpf_valido?(cpf)
 cpf.match?(/\A\d{11}\z/) && !cpf.match?(/\A(\d)\1{10}\z/)
 end
 end
end
```

---

## Model e migration para armazenar resultados

Crie o model e a migration para persistir os resultados das consultas.

```ruby
# db/migrate/XXXXXX_create_consultas_cpf.rb
class CreateConsultasCpf < ActiveRecord::Migration[7.1]
 def change
 create_table :consultas_cpf do |t|
 t.string :cpf, null: false, index: true
 t.string :nome
 t.string :genero
 t.date :data_nascimento
 t.string :status, null: false, default: "pendente"
 t.text :motivo_falha
 t.datetime :consultado_em

 t.timestamps
 end

 add_index :consultas_cpf, [:cpf, :created_at]
 add_index :consultas_cpf, :status
 end
end

# app/models/consulta_cpf.rb
class ConsultaCpf < ApplicationRecord
 validates :cpf, presence: true
 validates :status, inclusion: {
 in: %w[pendente sucesso falha]
 }

 scope :recente, -> { where("created_at > ?", 24.hours.ago) }
 scope :sucesso, -> { where(status: "sucesso") }
 scope :falha, -> { where(status: "falha") }

 def sucesso?
 status == "sucesso"
 end
end
```

| Campo | Tipo | Descrição |
|---|---|---|
| cpf | string | CPF consultado (11 dígitos) |
| nome | string | Nome retornado pela API |
| genero | string | Gênero retornado pela API |
| data_nascimento | date | Data de nascimento retornada |
| status | string | pendente, sucesso ou falha |
| motivo_falha | text | Motivo em caso de falha |
| consultado_em | datetime | Momento da consulta à API |

---

## Monitoramento e métricas

Monitore os jobs para garantir que o processamento está saudável.

```ruby
# app/services/metricas_cpf_service.rb
class MetricasCpfService
 def self.resumo
 {
 total_consultas: ConsultaCpf.count,
 sucessos: ConsultaCpf.sucesso.count,
 falhas: ConsultaCpf.falha.count,
 taxa_sucesso: calcular_taxa_sucesso,
 filas: {
 urgente: Sidekiq::Queue.new("consulta_cpf_urgente").size,
 padrao: Sidekiq::Queue.new("consulta_cpf_padrao").size,
 lote: Sidekiq::Queue.new("consulta_cpf_lote").size
 },
 workers_ativos: Sidekiq::Workers.new.size
 }
 end

 def self.calcular_taxa_sucesso
 total = ConsultaCpf.count
 return 0 if total.zero?

 ((ConsultaCpf.sucesso.count.to_f / total) * 100).round(2)
 end
end
```

---

## Perguntas frequentes

### Por que usar ActiveJob em vez de processar consultas de CPF de forma síncrona no controller?
Consultas síncronas à API externa dentro de uma requisição HTTP podem demorar até 1-2 segundos em condições normais — e muito mais em picos ou instabilidade de rede. Com ActiveJob e Sidekiq, o controller responde em milissegundos e o processamento real ocorre no worker em background, sem afetar o tempo de resposta da aplicação para o usuário.

### Como configurar o número de workers Sidekiq para processar consultas de CPF?
Defina `concurrency` no `sidekiq.yml` de acordo com o volume esperado. Para o plano Pro da [**CPFHub.io**](https://www.cpfhub.io/) (1.000 consultas/mês), uma concorrência de 5 a 10 workers já é suficiente. Se o volume ultrapassar o limite do plano, a API não bloqueia — cobra R$0,15 por consulta adicional. Escale os workers conforme o volume crescer.

### Como garantir conformidade com a LGPD ao armazenar resultados de consultas de CPF no banco?
Armazene apenas os campos necessários para a finalidade declarada, não o CPF em texto puro se um identificador interno bastar, implemente controle de acesso às tabelas de consulta e defina política de retenção com exclusão automática após o prazo necessário. A [ANPD](https://www.gov.br/anpd) orienta que o princípio da necessidade se aplica tanto à coleta quanto ao armazenamento de dados pessoais.

### O que acontece se o Sidekiq reiniciar durante o processamento de um job de CPF?
O Sidekiq usa o Redis para rastrear jobs em andamento. Se um worker for interrompido durante o processamento, o job retorna à fila após o timeout de visibilidade e é retentado automaticamente conforme a configuração de `retry_on` no job. Para evitar registros duplicados no banco, verifique a existência do CPF antes de salvar (upsert ou find_or_create_by).

### Leia também

- [SLA de API de CPF: níveis de disponibilidade e o que exigir do seu provedor](https://cpfhub.io/blog/sla-api-cpf-niveis-disponibilidade)
- [Como validar CPF no frontend com React e API REST](https://cpfhub.io/blog/como-validar-cpf-no-frontend-com-react-e-api-rest)
- [10 erros mais comuns ao integrar uma API de CPF e como evitá-los](https://cpfhub.io/blog/10-erros-mais-comuns-ao-integrar-uma-api-de-cpf)
- [API de CPF grátis para desenvolvedores: como começar em 5 minutos](https://cpfhub.io/blog/api-cpf-gratis-desenvolvedores-comecar-5-minutos)

---

## Conclusão

O ActiveJob com Sidekiq oferece uma solução elegante e robusta para processar consultas de CPF em segundo plano no Rails. Com retry automático, filas priorizadas e persistência de resultados, o sistema garante que nenhuma consulta seja perdida e que a aplicação responda rapidamente ao usuário. A separação entre produtores e consumidores permite escalar o processamento de forma independente da aplicação web.

Cadastre-se em [cpfhub.io](https://www.cpfhub.io/) — 50 consultas mensais gratuitas, sem cartão de crédito — e comece a processar consultas de CPF em background na sua aplicação Rails hoje mesmo.

