Wednesday

18-06-2025 Vol 19

Como usamos Oban com Elixir para resolver nossas rotinas de faturamento

Como Utilizamos Oban com Elixir para Otimizar Nossas Rotinas de Faturamento

Neste artigo, exploraremos detalhadamente como implementamos o Oban, uma biblioteca de processamento de tarefas em segundo plano robusta para Elixir, para modernizar e otimizar nossas rotinas de faturamento. Abordaremos os desafios que enfrentávamos antes da adoção do Oban, os benefícios que obtivemos e um passo a passo de como configuramos e utilizamos o Oban em nossa aplicação Elixir para lidar com tarefas de faturamento de forma eficiente e confiável.

Índice

  1. Introdução: Desafios e Oportunidades no Faturamento
  2. O que é Oban e por que o Escolhemos?
  3. Configuração do Oban em um Projeto Elixir
  4. Definindo Trabalhos do Oban para Faturamento
  5. Agendamento de Tarefas de Faturamento com Oban
  6. Gerenciamento e Monitoramento de Trabalhos do Oban
  7. Tratamento de Erros e Retentativas
  8. Otimização do Desempenho do Oban
  9. Benefícios da Implementação do Oban para Faturamento
  10. Considerações Finais e Próximos Passos

1. Introdução: Desafios e Oportunidades no Faturamento

O faturamento é uma função crítica para qualquer negócio. Um processo de faturamento ineficiente pode levar a atrasos no pagamento, erros de cobrança, e insatisfação do cliente. Antes de implementarmos o Oban, enfrentávamos os seguintes desafios:

  • Tarefas Demoradas: Geração de faturas complexas e processamento de pagamentos levavam tempo, impactando a experiência do usuário.
  • Concorrência: Processar faturas simultaneamente causava gargalos e lentidão no sistema.
  • Falta de Confiabilidade: Falhas ocasionais resultavam em faturas não processadas e perdas financeiras.
  • Dificuldade em Monitorar: Não tínhamos uma visão clara do status das tarefas de faturamento.

Identificamos uma oportunidade de melhorar significativamente nosso processo de faturamento através da implementação de uma solução de processamento de tarefas em segundo plano. Queríamos uma solução que fosse confiável, escalável, e fácil de integrar com nossa aplicação Elixir existente.

2. O que é Oban e por que o Escolhemos?

Oban é uma biblioteca para Elixir que facilita o processamento de tarefas em segundo plano. Ele oferece os seguintes recursos:

  • Confiabilidade: Garante que as tarefas sejam executadas, mesmo em caso de falhas.
  • Escalabilidade: Permite processar um grande volume de tarefas simultaneamente.
  • Flexibilidade: Suporta diferentes tipos de trabalhos e estratégias de agendamento.
  • Monitoramento: Fornece informações detalhadas sobre o status das tarefas.

Escolhemos o Oban pelas seguintes razões:

  1. Integração Nativa com Elixir: O Oban é construído especificamente para Elixir, o que facilita a integração com nosso código existente.
  2. Confiabilidade e Resiliência: O Oban utiliza o PostgreSQL para persistir os trabalhos, garantindo que as tarefas sejam executadas mesmo em caso de falhas do sistema.
  3. Facilidade de Uso: A API do Oban é clara e concisa, tornando fácil definir e agendar trabalhos.
  4. Comunidade Ativa: O Oban possui uma comunidade ativa e documentação abrangente.
  5. Suporte para Priorização: Podemos priorizar tarefas de faturamento críticas para garantir que sejam processadas primeiro.

3. Configuração do Oban em um Projeto Elixir

Para configurar o Oban em um projeto Elixir, siga os seguintes passos:

3.1. Adicionar a Dependência do Oban

Adicione a dependência do Oban ao arquivo `mix.exs`:

def deps do
    [
      {:oban, "~> 2.15"}
    ]
  end

Em seguida, execute o comando `mix deps.get` para baixar as dependências.

3.2. Configurar o Oban

Configure o Oban no arquivo `config/config.exs`:

config :my_app, Oban,
    repo: MyApp.Repo,
    plugins: [Oban.Plugins.Pruner],
    queues: [
      default: 5,
      billing: 10
    ]

Nesta configuração, definimos o repositório do Ecto que o Oban utilizará para persistir os trabalhos. Também configuramos duas filas: `default` e `billing`. A fila `billing` terá prioridade maior (10) do que a fila `default` (5).

O plugin `Oban.Plugins.Pruner` é configurado para remover trabalhos antigos do banco de dados, mantendo o banco de dados limpo e eficiente.

3.3. Criar a Tabela do Oban

Crie a tabela do Oban no banco de dados utilizando as migrações do Ecto:

mix ecto.gen.migration create_oban_jobs
  

Edite a migração gerada para adicionar a tabela `oban_jobs`:

defmodule MyApp.Repo.Migrations.CreateObanJobs do
  use Ecto.Migration

  def change do
    Oban.Migration.create_oban_jobs()
  end
end

Execute a migração:

mix ecto.migrate
  

3.4. Iniciar o Oban

Adicione o Oban à lista de supervisores no arquivo `lib/my_app/application.ex`:

defmodule MyApp.Application do
  use Application

  def start(_type, _args) do
    children = [
      MyApp.Repo,
      {Oban, Application.fetch_env!(:my_app, Oban)},
      MyAppWeb.Endpoint
    ]

    opts = [strategy: :one_for_one, name: MyApp.Supervisor]
    Supervisor.start_link(children, opts)
  end
end

Com estes passos, o Oban está configurado e pronto para ser utilizado em seu projeto Elixir.

4. Definindo Trabalhos do Oban para Faturamento

Para utilizar o Oban, você precisa definir os trabalhos que serão executados em segundo plano. Para o faturamento, podemos definir os seguintes trabalhos:

  • `GenerateInvoiceJob`: Gera a fatura para um cliente específico.
  • `ProcessPaymentJob`: Processa o pagamento de uma fatura.
  • `SendInvoiceEmailJob`: Envia a fatura por e-mail para o cliente.
  • `UpdateSubscriptionJob`: Atualiza o status da assinatura do cliente.

Aqui está um exemplo de como definir o trabalho `GenerateInvoiceJob`:

defmodule MyApp.Jobs.GenerateInvoiceJob do
  use Oban.Worker

  @impl Oban.Worker
  def perform(%{args: %{"customer_id" => customer_id}}) do
    customer = MyApp.Accounts.get_customer!(customer_id)

    invoice =
      MyApp.Billing.generate_invoice(customer)
      |> MyApp.Repo.insert!()

    Logger.info("Fatura gerada com sucesso para o cliente #{customer_id}")

    %{invoice: invoice}
  end
end

Neste exemplo, o trabalho `GenerateInvoiceJob` recebe o `customer_id` como argumento. Ele busca o cliente no banco de dados, gera a fatura, e a persiste no banco de dados. Em seguida, ele registra uma mensagem de log informando que a fatura foi gerada com sucesso.

5. Agendamento de Tarefas de Faturamento com Oban

Para agendar tarefas de faturamento com o Oban, utilize a função `Oban.insert/1`. Por exemplo, para agendar a geração de uma fatura para um cliente específico, faça o seguinte:

customer = MyApp.Accounts.get_customer!(customer_id)

  Oban.insert(%{
    worker: MyApp.Jobs.GenerateInvoiceJob,
    args: %{"customer_id" => customer.id},
    queue: "billing",
    priority: 5,
    attempt: 0
  })

Neste exemplo, estamos inserindo um novo trabalho na fila `billing` com prioridade 5. Estamos passando o `customer_id` como argumento para o trabalho `GenerateInvoiceJob`.

5.1. Agendamento Regular de Faturas

Para agendar a geração de faturas regularmente, você pode utilizar a biblioteca `Quantum` ou `Cronex`. Aqui está um exemplo de como agendar a geração de faturas mensalmente utilizando o `Quantum`:

  1. Adicione a Dependência do Quantum:
  2. def deps do
          [
            {:quantum, "~> 3.0"}
          ]
        end
        

    Execute `mix deps.get`.

  3. Configure o Quantum:
  4. Adicione a seguinte configuração ao arquivo `config/config.exs`:

    config :my_app, Quantum,
          jobs: [
            {"0 0 1 * *", {MyApp.Billing, :schedule_monthly_invoices, []}}
          ]
        

    Esta configuração agenda a função `MyApp.Billing.schedule_monthly_invoices` para ser executada todo dia 1 de cada mês às 00:00.

  5. Implemente a Função de Agendamento:
  6. Implemente a função `schedule_monthly_invoices` no módulo `MyApp.Billing`:

    defmodule MyApp.Billing do
      def schedule_monthly_invoices do
        MyApp.Accounts.list_customers()
        |> Enum.each(fn customer ->
          Oban.insert(%{
            worker: MyApp.Jobs.GenerateInvoiceJob,
            args: %{"customer_id" => customer.id},
            queue: "billing",
            priority: 5,
            attempt: 0
          })
        end)
      end
    end
    

    Esta função busca todos os clientes e agenda a geração de uma fatura para cada um deles.

6. Gerenciamento e Monitoramento de Trabalhos do Oban

O Oban fornece diversas ferramentas para gerenciar e monitorar os trabalhos. Você pode utilizar a interface web do Oban ou a API para interagir com os trabalhos.

6.1. Interface Web do Oban

O ObanUI fornece uma interface web para monitorar e gerenciar os trabalhos. Para habilitar o ObanUI, adicione a dependência `oban_web` ao arquivo `mix.exs`:

def deps do
    [
      {:oban_web, "~> 0.6"}
    ]
  end
  

Execute `mix deps.get`.

Adicione a seguinte rota ao arquivo `lib/my_app_web/router.ex`:

scope "/", MyAppWeb do
    pipe_through :browser

    live "/oban", ObanWeb.Endpoint
  end
  

Agora você pode acessar a interface web do Oban em `/oban`.

6.2. API do Oban

O Oban fornece uma API para interagir com os trabalhos. Você pode utilizar as funções `Oban.get_by_id/1`, `Oban.cancel/1`, e `Oban.retry/1` para gerenciar os trabalhos.

Por exemplo, para cancelar um trabalho com ID `123`, faça o seguinte:

Oban.cancel(123)
  

7. Tratamento de Erros e Retentativas

É importante tratar erros e implementar retentativas para garantir que as tarefas sejam executadas com sucesso, mesmo em caso de falhas temporárias.

7.1. Retentativas Automáticas

O Oban suporta retentativas automáticas. Por padrão, o Oban tentará executar um trabalho até 20 vezes, com um atraso exponencial entre as tentativas.

Você pode configurar o número de tentativas e o atraso entre as tentativas utilizando as opções `max_attempts` e `backoff` na definição do trabalho:

defmodule MyApp.Jobs.GenerateInvoiceJob do
  use Oban.Worker

  @impl Oban.Worker
  def perform(%{args: %{"customer_id" => customer_id}}) do
    # ...

    {:error, :failed_to_generate_invoice}
  end

  def retry(_job, _attempt) do
    {:ok, backoff: :exponential}
  end
end

Neste exemplo, estamos retornando `{:error, :failed_to_generate_invoice}` quando a geração da fatura falha. A função `retry/2` é chamada para determinar se o trabalho deve ser retentado. Neste caso, estamos utilizando um backoff exponencial, o que significa que o atraso entre as tentativas aumentará exponencialmente.

7.2. Tratamento de Erros Específicos

Você pode tratar erros específicos e tomar ações diferentes dependendo do tipo de erro. Por exemplo, se a geração da fatura falhar devido a um problema de conexão com o banco de dados, você pode retentar o trabalho. Se a geração da fatura falhar devido a dados inválidos, você pode registrar o erro e notificar um administrador.

defmodule MyApp.Jobs.GenerateInvoiceJob do
  use Oban.Worker

  @impl Oban.Worker
  def perform(%{args: %{"customer_id" => customer_id}}) do
    try do
      # ...
    rescue
      Ecto.ConstraintError ->
        Logger.error("Erro de constraint ao gerar fatura para o cliente #{customer_id}")
        {:error, :constraint_error}

      e ->
        Logger.error("Erro ao gerar fatura para o cliente #{customer_id}: #{inspect(e)}")
        {:error, :generic_error}
    end
  end

  def retry(_job, _attempt) do
    case _job.state do
      :constraint_error ->
        {:stop, :skip}  # Não retentar se o erro for um problema de constraint
      _ ->
        {:ok, backoff: :exponential}  # Retentar com backoff exponencial para outros erros
    end
  end
end

Neste exemplo, estamos capturando exceções `Ecto.ConstraintError` e registrando o erro. Retornamos `{:stop, :skip}` para indicar que o trabalho não deve ser retentado se o erro for um problema de constraint. Para outros erros, retornamos `{:ok, backoff: :exponential}` para retentar o trabalho com um backoff exponencial.

8. Otimização do Desempenho do Oban

Para otimizar o desempenho do Oban, considere as seguintes dicas:

  • Ajuste o Número de Filas e Workers: Aumente o número de filas e workers para processar um maior volume de tarefas simultaneamente.
  • Utilize Índices no Banco de Dados: Crie índices nos campos utilizados pelas consultas do Oban para acelerar as consultas.
  • Monitore o Desempenho do Banco de Dados: Monitore o desempenho do banco de dados e otimize as consultas para evitar gargalos.
  • Utilize Cache: Utilize cache para armazenar dados frequentemente acessados para reduzir a carga no banco de dados.
  • Divida Tarefas Grandes em Tarefas Menores: Divida tarefas grandes em tarefas menores para evitar bloqueios prolongados e melhorar a capacidade de resposta do sistema.
  • Considere a utilização de Particionamento: Se você tiver um grande volume de dados, considere utilizar particionamento no banco de dados para melhorar o desempenho das consultas.
  • Analise os Logs do Oban: Monitore os logs do Oban para identificar gargalos e erros.

9. Benefícios da Implementação do Oban para Faturamento

A implementação do Oban para nossas rotinas de faturamento nos proporcionou os seguintes benefícios:

  • Melhora na Confiabilidade: O Oban garante que as tarefas de faturamento sejam executadas, mesmo em caso de falhas.
  • Aumento da Escalabilidade: O Oban permite processar um grande volume de tarefas simultaneamente, garantindo que o sistema possa lidar com picos de demanda.
  • Redução do Tempo de Processamento: O processamento em segundo plano permite que os usuários continuem utilizando a aplicação enquanto as tarefas de faturamento são executadas.
  • Melhora na Experiência do Usuário: A geração de faturas e o processamento de pagamentos são feitos de forma mais rápida e confiável, melhorando a experiência do usuário.
  • Facilidade de Monitoramento: A interface web do Oban fornece informações detalhadas sobre o status das tarefas, permitindo que os administradores monitorem e gerenciem as tarefas de forma eficiente.

10. Considerações Finais e Próximos Passos

A implementação do Oban para nossas rotinas de faturamento foi um grande sucesso. O Oban nos permitiu modernizar e otimizar nossos processos, resultando em melhorias significativas na confiabilidade, escalabilidade, e experiência do usuário.

Como próximos passos, planejamos explorar os seguintes recursos do Oban:

  • Trabalhos Agendados: Utilizar o Oban para agendar tarefas de faturamento em horários específicos.
  • Trabalhos de Lote: Processar um lote de faturas em paralelo utilizando o Oban.
  • Integração com Sistemas Externos: Integrar o Oban com sistemas externos, como gateways de pagamento e sistemas de contabilidade.
  • Monitoramento Avançado: Implementar monitoramento avançado para rastrear o desempenho das tarefas de faturamento e identificar gargalos.

Esperamos que este artigo tenha sido útil para você. Se você tiver alguma dúvida ou comentário, sinta-se à vontade para entrar em contato conosco.

Recursos Adicionais:

“`

omcoding

Leave a Reply

Your email address will not be published. Required fields are marked *