Blog SAPienteSAPiente
ABAP Cloud

Application Jobs no SAP ABAP Cloud: como criar, agendar e logar jobs em background

SAPiente12 de jun. de 2026· 5 min read

Do SM36/SM37 para o Clean Core

Quem vem do ABAP clássico agenda job com SM36 e monitora com SM37 — apontando para um relatório SE38 com uma variant de seleção. No ABAP Cloud esse caminho simplesmente não existe: SM36, SM37 e relatórios executáveis não fazem parte da linguagem liberada. O substituto é o Application Job Framework (AJF).

O AJF separa a lógica (uma classe ABAP) do agendamento (um template reutilizável) e da execução (o app Fiori "Application Jobs"). É a forma Clean Core de rodar processamento em background — e funciona igual no S/4HANA Cloud Public Edition, no ABAP Environment (BTP) e, desde os releases recentes, também no on-premise e Private Cloud.

Este guia mostra o que é um Application Job, o que você precisa para usar, como montar a classe com as interfaces certas, como agendar pelo Fiori e — o ponto que costuma faltar nos tutoriais — como deixar o log já implementado, anexado ao próprio job, com uma classe-modelo pronta para copiar.

O essencial em 5 linhas

  • Application Job é o substituto Clean Core do par SM36/SM37 + relatório SE38; nada de transação clássica.

  • A lógica vive numa classe ABAP que implementa if_apj_dt_exec_object (parâmetros) e if_apj_rt_exec_object (execução) — ou a nova if_apj_rt_run.

  • Para agendar você precisa de um Job Catalog Entry + um Job Template.

  • O usuário agenda e monitora pelos apps Fiori "Application Jobs" e "Monitor Application Jobs".

  • O log usa a Application Log API (cl_bali_*) e, com assign_to_current_appl_job = abap_true, aparece dentro do próprio job.

O que é um Application Job?

Um Application Job é uma tarefa de fundo definida e executada pelo Application Job Framework, sem depender de transações clássicas de scheduling. Em vez de "agendar um relatório", você expõe uma classe ABAP como uma tarefa parametrizável e reutilizável que o usuário de negócio agenda por uma interface Fiori (SAP Help — Application Jobs).

A diferença de mentalidade importa. No mundo clássico, programa e agendamento eram a mesma coisa. No AJF eles são desacoplados: a classe sabe o que fazer, o template define quais parâmetros aparecem na tela e quem pode usar, e o app Fiori decide quando rodar. Esse desacoplamento é justamente o que torna o modelo seguro, reutilizável e Clean Core.

Regra mental: se no clássico você pensaria "crio um SE38 e agendo no SM36", no ABAP Cloud você pensa "crio uma classe com duas interfaces e publico um Job Template".

Como funciona um Application Job por dentro?

Um Application Job não é um objeto único — são quatro peças que se encaixam, cada uma com um papel. Entender quem faz o quê evita o erro mais comum de iniciante: tentar colocar a lógica de seleção dentro do template, ou a autorização dentro da classe.

Peça

O que é

Onde se cria

Papel

Classe executora

Classe ABAP com as interfaces do AJF

ADT (Eclipse)

Define parâmetros e contém a lógica do job

Job Catalog Entry

Catálogo que aponta para a classe

ADT (Eclipse)

Liga o framework à sua classe executora

Job Template

Modelo agendável baseado no catalog entry

ADT (Eclipse)

É o que aparece para seleção no app Fiori

O fluxo em runtime: o usuário abre o app "Application Jobs", escolhe um Job Template, preenche os parâmetros (que vêm do get_parameters da sua classe), define recorrência e dispara. O framework instancia a classe e chama o método execute, passando os parâmetros preenchidos. Tudo que esse método logar pode ser amarrado ao registro do job.

O que é necessário para usar Application Jobs?

Os pré-requisitos são poucos, mas não-negociáveis. Sem o ambiente e os objetos certos, a classe nem ativa ou o template não aparece para agendamento. A boa notícia: a maior parte do trabalho de autorização é gerada para você.

  • Ambiente ABAP Cloud — S/4HANA Cloud Public Edition, SAP BTP ABAP Environment ou, em releases recentes, on-premise/Private Cloud com ABAP Cloud habilitado.

  • ADT (ABAP Development Tools no Eclipse) — o catalog entry e o template são objetos do repositório criados só pelo Eclipse, não por transação.

  • Uma classe implementando if_apj_dt_exec_object e if_apj_rt_exec_object (ou a interface única if_apj_rt_run, mais nova).

  • Objeto e Sub-objeto de Application Log — criados no Eclipse (não no SLG0) — se você quiser persistir log, que é o foco deste post.

  • Classe de mensagens (message class) para os textos do log — nunca hardcode texto na classe.

  • Autorização — a app de IAM é gerada junto com o catalog entry/template; basta atribuir o Business Catalog ao papel do usuário.

Atenção ao Application Log no Cloud: em ABAP Cloud o objeto e o sub-objeto de log não são criados pela SLG0. Você cria um objeto do tipo Application Log Object direto no Eclipse (New → Other ABAP Repository Object). É esse objeto/sub-objeto que entra nas constantes c_log_object e c_log_subobject da classe-modelo.

Como criar a classe do job (IF_APJ_DT e IF_APJ_RT_EXEC_OBJECT)?

A classe executora implementa duas interfaces, cada uma com um único método. if_apj_dt_exec_object~get_parameters é o lado design-time: descreve os parâmetros da tela de agendamento. if_apj_rt_exec_object~execute é o lado runtime: contém a lógica, e recebe os parâmetros já preenchidos pelo usuário.

A definição da classe-modelo declara as duas interfaces, os tipos, as constantes (inclusive as do log), o buffer de mensagens t_log e os métodos — abaixo uma classe exemplo de job já com logs implementados:

CLASS zcl_appjob DEFINITION
  PUBLIC
  FINAL
  CREATE PUBLIC.

  PUBLIC SECTION.
    INTERFACES if_apj_dt_exec_object.
    INTERFACES if_apj_rt_exec_object.

  PRIVATE SECTION.

    " Tipos usados nas assinaturas dos métodos
    TYPES: BEGIN OF ty_record,
             field1 TYPE string,
             field2 TYPE string,
           END OF ty_record.
    TYPES ty_t_record TYPE STANDARD TABLE OF ty_record WITH EMPTY KEY.

    " Classe de mensagens — todo texto de log vem daqui
    CONSTANTS c_msg_class     TYPE symsgid   VALUE 'ZXXXX'.

    " Objeto e Sub-objeto de Application Log (criados via Eclipse, não SLG0)
    CONSTANTS c_log_object    TYPE balobj_d  VALUE 'ZXXXXX'.
    CONSTANTS c_log_subobject TYPE balsubobj VALUE 'ZSUBOBJ'.

    " Default da seleção
    CONSTANTS c_bukrs_default TYPE bukrs     VALUE '1000'.

    " Buffer interno de log — preenchido por m_add_log, gravado por m_save_log
    DATA t_log TYPE bapirettab.

    " Método de Processamento
    METHODS process
      IMPORTING i_v_param         TYPE string
      RETURNING VALUE(r_v_result) TYPE abap_bool.

    " Infraestrutura de log — igual em toda classe de AppJob
    METHODS add_log
      IMPORTING i_v_type   TYPE symsgty
                i_v_id     TYPE symsgid
                i_v_number TYPE symsgno
                i_v_v1     TYPE symsgv DEFAULT space
                i_v_v2     TYPE symsgv DEFAULT space
                i_v_v3     TYPE symsgv DEFAULT space
                i_v_v4     TYPE symsgv DEFAULT space.

    METHODS m_save_log.

ENDCLASS.

Por que separar m_add_log de m_save_log?

Porque gravar log a cada mensagem é caro e atrapalha o COMMIT do job. O padrão correto é acumular tudo num buffer em memória (t_log) durante a execução e persistir uma vez só, no fim. add_log apenas enfileira; save_log escreve no banco. Isso também garante que um erro de gravação de log nunca derrube o resultado de negócio.

Como definir os parâmetros da tela de agendamento?

O método get_parameters preenche duas tabelas: et_parameter_def (a definição de cada campo) e et_parameter_val (os valores default pré-preenchidos). O atributo kind decide se o campo é um valor único (parameter, vira P_*) ou um range com intervalo (select_option, vira S_*), exatamente como PARAMETERS vs SELECT-OPTIONS do clássico.

CLASS zcl_appjob IMPLEMENTATION.

  METHOD if_apj_dt_exec_object~get_parameters.

    " Definição — o que aparece na tela de agendamento
    "   kind = if_apj_dt_exec_object=>parameter     -> valor único (P_*)
    "   kind = if_apj_dt_exec_object=>select_option -> range      (S_*)
    et_parameter_def = VALUE #( mandatory_ind  = abap_true
                                changeable_ind = abap_true
                                ( selname    = 'P_BUKRS'
                                  kind       = if_apj_dt_exec_object=>parameter
                                  param_text = 'Empresa'
                                  datatype   = 'BUKRS'
                                  length     = 4 ) ).

    " Valores default pré-preenchidos na tela
    et_parameter_val = VALUE #( sign   = 'I'
                                option = 'EQ'
                                ( selname = 'P_BUKRS'
                                  kind   = if_apj_dt_exec_object=>parameter
                                  low    = c_bukrs_default ) ).

  ENDMETHOD.

Dica: use select_option sempre que o usuário precisar informar intervalos ou múltiplos valores (datas, faixas de documento). Use parameter para chaves únicas obrigatórias, como a empresa. O datatype deve ser um Data Element existente — é dele que a tela herda o label e o value help.

Como ler os parâmetros e estruturar o execute?

No execute, os valores preenchidos chegam em it_parameters. A leitura recomendada é sempre por LOOP + CASE sobre selname, montando ranges — nunca um acesso direto por índice, porque a ordem não é garantida. A estrutura-modelo divide a execução em seis passos, com log em cada ponto relevante:

  METHOD if_apj_rt_exec_object~execute.

    DATA lr_bukrs TYPE RANGE OF bukrs.

*** Passo 1 — Ler parâmetros (sempre LOOP + CASE, nunca índice direto)
    LOOP AT it_parameters ASSIGNING FIELD-SYMBOL(<lfs_par>).
      CASE <lfs_par>-selname.
        WHEN 'P_BUKRS'.
          APPEND VALUE #( sign   = <lfs_par>-sign
                          option = <lfs_par>-option
                          low    = <lfs_par>-low
                          high   = <lfs_par>-high ) TO lr_bukrs.
      ENDCASE.
    ENDLOOP.

*** Passo 2 — Validar obrigatórios (loga erro e sai; nunca segue com chave vazia)
    IF lr_bukrs IS INITIAL.
      add_log( i_v_type = 'E' i_v_id = c_msg_class i_v_number = '001' ).
      save_log( ).
      RETURN.
    ENDIF.

*** Passo 3 — Logar início do job (ajuda muito no monitoramento)
    add_log( i_v_type   = 'I'
             i_v_id     = c_msg_class
             i_v_number = '002'   " Job iniciado para empresa &1
             i_v_v1     = CONV symsgv( lr_bukrs[ 1 ]-low ) ).

*** Passo 4 — Lógica de negócio (delegada a métodos privados)
    DATA(lt_data) = m_select_data( i_v_bukrs = CONV bukrs( lr_bukrs[ 1 ]-low ) ).

    IF lt_data IS INITIAL.
      add_log( i_v_type = 'I' i_v_id = c_msg_class i_v_number = '003' ).
      save_log( ).
      RETURN.
    ENDIF.

*** Passo 5 — Processar registro a registro
***          Um erro num registro NÃO pode parar o job inteiro
    LOOP AT lt_data ASSIGNING FIELD-SYMBOL(<lfs_record>).
      IF process( i_v_param = <lfs_record>-field1 ) = abap_true.
        add_log( i_v_type = 'S' i_v_id = c_msg_class i_v_number = '030'
                 i_v_v1   = CONV symsgv( <lfs_record>-field1 ) ).
      ELSE.
        add_log( i_v_type = 'E' i_v_id = c_msg_class i_v_number = '031'
                   i_v_v1   = CONV symsgv( <lfs_record>-field1 ) ).
      ENDIF.
    ENDLOOP.

*** Passo 6 — Logar fim e persistir TUDO (m_save_log é sempre a última chamada)
    add_log( i_v_type = 'I' i_v_id = c_msg_class i_v_number = '005' ).
    save_log( ).

  ENDMETHOD.

Padrão crítico: dentro do LOOP de processamento, capture o erro de cada registro e logue — não deixe uma exceção abortar o job inteiro. Um job de fim de mês que para no registro 12 de 5.000 por causa de um dado ruim é um chamado garantido. Logue o registro problemático e siga.

Como implementar o log e anexá-lo ao Application Job?

Aqui está o coração do modelo. O log usa a Application Log API liberada para ABAP Cloud (pacote SZAL_API): cl_bali_log é o log, os setters (cl_bali_header_setter, cl_bali_message_setter) montam cabeçalho e itens, e cl_bali_log_db persiste. O parâmetro assign_to_current_appl_job = abap_true é o que faz a mágica: ele amarra o log ao job em execução, então as mensagens aparecem direto no app "Application Jobs", sem precisar abrir o SLG1 à parte.

add_log é trivial — só enfileira uma bapiret2 no buffer:

  METHOD add_log.
    " Tipos: 'E' Erro | 'W' Aviso | 'I' Info | 'S' Sucesso
    " Apenas enfileira no buffer. NÃO grava — isso é trabalho do save_log( ).
    APPEND VALUE bapiret2( type       = i_v_type
                           id         = i_v_id
                           number     = i_v_number
                           message_v1 = i_v_v1
                           message_v2 = i_v_v2
                           message_v3 = i_v_v3
                           message_v4 = i_v_v4 ) TO t_log.
  ENDMETHOD.

E m_save_log percorre o buffer, converte cada bapiret2 num item de log com a severidade correta, e grava tudo de uma vez já anexado ao job. Repare no SWITCH que mapeia o tipo da mensagem para a constante de severidade — note que sucesso ('S') vira c_severity_status, pois a API não tem uma severidade "success" separada:

  METHOD save_log.

    DATA lo_head TYPE REF TO if_bali_header_setter.
    DATA lo_log  TYPE REF TO if_bali_log.
    DATA lo_db   TYPE REF TO if_bali_log_db.

    " Nada a gravar — sai em silêncio
    IF t_log IS INITIAL.
      RETURN.
    ENDIF.

    TRY.
        " Cabeçalho do log com Objeto + Sub-objeto
        lo_head = cl_bali_header_setter=>create( object    = c_log_object
                                                 subobject = c_log_subobject ).
        lo_log  = cl_bali_log=>create_with_header( lo_head ).

        " Cada mensagem do buffer vira um item do log
        LOOP AT t_log ASSIGNING FIELD-SYMBOL(<lfs_log>).
          DATA(lo_item) = cl_bali_message_setter=>create(
            severity   = SWITCH #( <lfs_log>-type
                           WHEN 'E' THEN if_bali_constants=>c_severity_error
                           WHEN 'W' THEN if_bali_constants=>c_severity_warning
                           WHEN 'S' THEN if_bali_constants=>c_severity_status
                           WHEN 'I' THEN if_bali_constants=>c_severity_information
                           ELSE          if_bali_constants=>c_severity_information )
            id         = <lfs_log>-id
            number     = <lfs_log>-number
            variable_1 = <lfs_log>-message_v1
            variable_2 = <lfs_log>-message_v2
            variable_3 = <lfs_log>-message_v3
            variable_4 = <lfs_log>-message_v4 ).
          lo_log->add_item( lo_item ).
        ENDLOOP.

        " Persiste e amarra ao job atual (visível no app "Application Jobs")
        lo_db = cl_bali_log_db=>get_instance( ).
        lo_db->save_log( log                        = lo_log
                         assign_to_current_appl_job = abap_true ).
        COMMIT WORK.

      CATCH cx_bali_runtime cx_root.
        " Falha ao gravar log é não-fatal — o resultado de negócio é preservado
        RETURN.
    ENDTRY.

    CLEAR t_log.   " Limpa o buffer após gravar

  ENDMETHOD.

ENDCLASS.

Por que esse modelo funciona bem: toda a complexidade do log fica isolada em dois métodos que você copia como estão para qualquer novo job. A classe nova só muda os parâmetros, as constantes (c_log_object, c_log_subobject, c_msg_class) e a lógica de negócio. O log "vem de brinde" — e amarrado ao job, sem passo manual de monitoramento.

Como montar a classe de mensagens do log?

Como nenhum texto é hardcoded, você precisa criar os números de mensagem na message class ZXXXX antes de ativar a classe. Os placeholders &1 a &4 recebem os valores passados em i_v_v1i_v_v4. Esta é a tabela de referência do modelo:

Nr

Tipo

Texto (placeholders &1 &2 &3 &4)

001

E

Empresa obrigatória. Informe o parâmetro P_BUKRS.

002

I

Job iniciado para empresa &1.

003

I

Nenhum registro encontrado para processamento.

004

I

&1 registro(s) encontrado(s) para processamento.

005

I

Job finalizado.

030

S

Documento &1 processado com sucesso.

031

E

Erro ao processar documento &1. Verifique o log.

032

E

Documento &1: &2

999

E

Erro inesperado: &1

Como criar o Job Catalog Entry e o Job Template?

Com a classe ativa, faltam dois objetos de repositório, ambos criados no Eclipse via New → Other ABAP Repository Object. O Job Catalog Entry aponta para a sua classe executora — é a ponte entre o framework e o código. O Job Template é criado a partir do catalog entry e é o objeto que efetivamente aparece para seleção no app Fiori (SAP Community — Application Jobs End-to-End Guide).

Job Catalog Entry: informe a classe (zcl_appjob) e ative. Os parâmetros do get_parameters são reconhecidos automaticamente.

Job Catolog Entry Criado

Job Template: baseado no catalog entry, define como o job é exposto para agendamento. Ao salvar/ativar, a app de IAM (Business Catalog) é gerada automaticamente — você só precisa atribuí-la ao papel do usuário.

Job Template Criado

Como agendar e monitorar pelo Fiori?

O usuário de negócio não toca em código: ele usa o app "Application Jobs" para criar o agendamento e o "Monitor Application Jobs" para acompanhar.

  • No app "Application Jobs", clique em Create, escolha o seu Job Template e preencha os parâmetros (que vieram do get_parameters).

  • Em Scheduling Options, escolha rodar imediatamente ou defina a recorrência (diária, semanal, padrão de horário).

  • Em seguida informe os paramêtros quando houver.

  • Depois de iniciado, o "Monitor Application Jobs" mostra status, duração e — graças ao assign_to_current_appl_job — o log completo da execução ali mesmo.

Dica de auditoria: como o DCL filtra leituras também dentro do job, processamentos técnicos que precisam enxergar tudo (fechamentos, integrações) podem usar WITH PRIVILEGED ACCESS com cautela. Veja os detalhes no guia de autorização no RAP (DCL, Global e Instance).

E a nova interface IF_APJ_RT_RUN?

A partir do release 2502 do Public Cloud (e nas versões full subsequentes para Private Cloud/on-premise), a SAP introduziu a interface única if_apj_rt_run, que unifica design-time e runtime num só lugar, com um método execute (SAP Help — Creating an ABAP Class with Interface IF_APJ_RT_RUN). O par if_apj_dt_exec_object + if_apj_rt_exec_object continua válido e amplamente usado; a classe-modelo deste post adota o par clássico por ser compatível com a base instalada mais ampla. A lógica de log (add_log/save_log) é idêntica em qualquer das duas abordagens.

Quais são as armadilhas mais comuns?

A maioria dos problemas com Application Jobs vem de poucos enganos recorrentes. Conhecê-los antes economiza horas de depuração — e evita ativações que falham.

  • Criar o log object pela SLG0 → no ABAP Cloud ele é criado no Eclipse. O SLG0 nem está liberado.

  • Ler it_parameters por índice → a ordem não é garantida; use sempre LOOP + CASE em selname.

  • Gravar log a cada mensagem → acumule no buffer e grave uma vez com m_save_log no fim.

  • Esquecer assign_to_current_appl_job = abap_true → o log grava, mas não aparece amarrado ao job.

  • Deixar uma exceção abortar o loop → trate por registro e logue; um dado ruim não pode derrubar o job inteiro.

  • Hardcode de texto → todo texto vem da message class; isso mantém o log traduzível e consistente.

  • Não atribuir o Business Catalog → a app de IAM é gerada, mas alguém precisa atribuí-la ao papel; senão o template não aparece para o usuário.

Perguntas frequentes

Application Job substitui mesmo o SM36/SM37?

Sim, no contexto Clean Core. No ABAP Cloud as transações clássicas de scheduling e os relatórios SE38 não fazem parte da linguagem liberada. O Application Job Framework é o caminho oficial: a lógica vira uma classe, o agendamento vira um Job Template e a operação acontece pelos apps Fiori "Application Jobs" e "Monitor Application Jobs".

Preciso das duas interfaces ou posso usar só IF_APJ_RT_RUN?

Depende do release. O par if_apj_dt_exec_object + if_apj_rt_exec_object funciona em toda a base instalada e é o usado na classe-modelo. A interface única if_apj_rt_run existe a partir do 2502 do Public Cloud e simplifica a estrutura, mas a lógica de log e parâmetros é a mesma. Escolha conforme a versão do seu sistema.

Por que meu log não aparece dentro do Application Job?

Quase sempre porque faltou assign_to_current_appl_job = abap_true na chamada do save_log. Sem esse parâmetro, o log é gravado, mas não fica amarrado ao job — você teria que buscá-lo separadamente. Com ele, as mensagens aparecem direto no "Monitor Application Jobs", junto do status da execução.

Como o usuário informa intervalos de datas ou múltiplos valores?

Use kind = if_apj_dt_exec_object=>select_option no get_parameters. Isso gera um campo de range (como SELECT-OPTIONS do clássico), com intervalo de/até e múltiplas linhas. Para chaves únicas obrigatórias, como a empresa, use kind = parameter, que gera um campo de valor único.

Onde crio o objeto de Application Log no ABAP Cloud?

No Eclipse, via New → Other ABAP Repository Object, escolhendo o tipo Application Log Object — não no SLG0, que não está liberado no ABAP Cloud. Você define o objeto e o sub-objeto e os referencia nas constantes c_log_object e c_log_subobject da classe. É esse par que a Application Log API usa no cabeçalho do log.

Conclusão

Application Job é o jeito Clean Core de rodar background: classe para a lógica, template para o agendamento, Fiori para a operação. Acerte a separação de papéis e o resto flui — parâmetros no get_parameters, lógica no execute, e nada de transação clássica.

O diferencial deste modelo é o log já pronto. Acumular mensagens em t_log, persistir uma vez com m_save_log e usar assign_to_current_appl_job = abap_true dá observabilidade real — o operador vê tudo dentro do próprio job, sem caçar log avulso. Copie m_add_log e m_save_log como estão, ajuste constantes e parâmetros, e cada novo job nasce com rastreabilidade de fábrica.

Referências oficiais

TagsClean CoreDesenvolvedorFuncional
Avalie este conteúdo
para avaliar
SAPiente
@sapiente

Blog sobre SAP, ABAP, BTP, Fiori e tudo que envolve o ecossistema SAP.

Ver perfil →
FacebookInstagramYouTubeTwitter

Comentários 0

Entre na conversa

Faça login para deixar seu comentário neste artigo.

Ainda sem comentários

Seja o primeiro a comentar este artigo.