RAP Unmanaged Scenario: como reutilizar BAPIs e lógica existente
No unmanaged, o RAP entrega a moldura e você entrega o miolo: buffer, CRUD e persistência via código legado. O espectro managed → unmanaged, as duas fases da transação e o passo a passo completo.
A ponte entre o legado e o ABAP Cloud
O unmanaged scenario é o tipo de implementação do RAP em que você escreve toda a lógica transacional e de persistência do business object — o framework fornece apenas a "moldura": o contrato REST, a orquestração da transação, o OData e a EML. Ele existe por um motivo central: reaproveitar lógica que já existe (BAPIs, function modules e regras de gravação legadas) e expô-la como um serviço RAP moderno e Clean Core.
A documentação oficial não faz rodeios: no unmanaged, "não há funcionalidade padrão fornecida pelo framework — é apenas a moldura de um business object, à qual o desenvolvedor precisa aderir". Você declara as operações na behavior definition, mas implementa cada uma manualmente em classes ABAP no behavior pool — inclusive o buffer transacional, normalmente delegado às próprias function modules legadas, que mantêm o estado em memória.
Se você vai criar um app do zero, sem nada para reutilizar, o managed é mais simples e exige muito menos código. A regra prática que fecha este post já vale de abertura: comece pelo managed sempre que puder, e traga o unmanaged quando houver algo que valha a pena reutilizar.
Antes do "versus": o espectro completo de persistência
A maioria dos textos apresenta managed vs. unmanaged como uma escolha binária — e ela não é. Entre os dois extremos existem duas variantes intermediárias do managed que resolvem casos que muita gente ataca (sem precisar) com um unmanaged completo:
Sabor | Interaction phase (buffer, CRUD) | Save (persistência) | Quando |
|---|---|---|---|
managed | Framework | Framework grava na | Greenfield puro: tabela própria, regras próprias |
managed with additional save | Framework | Framework grava + você adiciona passos no | Gravação padrão serve, mas falta algo no commit: change documents, application log, disparar evento |
managed with unmanaged save | Framework | Você grava no | O buffer e o CRUD do framework servem, mas a gravação é sua: BAPI, múltiplas tabelas, API externa |
unmanaged | Você (handler + buffer próprio/da FM) | Você (saver completo) | A lógica legada domina o ciclo inteiro — buffer, validação e gravação já existem nas FMs |
Detalhes que a documentação crava sobre as variantes intermediárias:
Ambas exigem a reimplementação do método
save_modifiedna saver class — e, nelas, o métodosaveclássico não pode ser usado.No unmanaged save, a BDEF não pode especificar
persistent table— afinal, quem grava é você.O
save_modifiedrecebe as instâncias a criar/alterar/excluir em parâmetros tipados com os BDEF derived typesTYPE REQUEST FOR CHANGEeTYPE REQUEST FOR DELETE; por padrão só chegam chaves + campos alterados — a adiçãowith full dataentrega a instância completa.A declaração vale para o BO inteiro (no header:
managed with unmanaged save implementation in class ...) ou por entidade (define behavior for Entidade with unmanaged save) — dá para misturar sabores no mesmo BO.
A pergunta certa antes de escolher unmanaged: o que é legado de verdade — o ciclo inteiro (buffer, validações, CRUD) ou só a gravação? Se é só a gravação, um managed with unmanaged save entrega o buffer, o draft e o CRUD de graça, e você implementa um único método. O unmanaged completo é para quando a FM legada já é dona do buffer.
Managed vs. unmanaged: a diferença que importa
Aspecto | Managed | Unmanaged |
|---|---|---|
Buffer transacional | O framework provê e gerencia | Você (ou a FM legada) gerencia |
CREATE / UPDATE / DELETE | Gerados automaticamente | Você implementa no handler |
READ | Out of the box | Você implementa (alimenta OData e ETag) |
LOCK | Framework (lock master) | Você implementa (tipicamente reusa o ENQUEUE da FM) |
Persistência (SAVE) | O framework grava na tabela | Você grava (saver), via FM/BAPI |
Esforço de desenvolvimento | Baixo | Alto |
Cenário ideal | App novo do zero (greenfield) | Reusar lógica existente (brownfield) |
Em ambos os casos você ganha a mesma "casca" do RAP: serviço OData (UI ou Web API), integração com Fiori Elements e acesso via EML. A diferença está em quem implementa o miolo transacional.
Quando usar: os casos concretos
Expor um cadastro legado como serviço: o cadastro vive há 15 anos em FMs de create/change/save com buffer próprio e ENQUEUE próprio — o unmanaged veste o RAP por cima sem reescrever nada.
Documento standard sem API released: a criação passa por BAPI clássica (com wrapper para o ABAP Cloud) e a persistência é dela, não sua — o BO unmanaged orquestra a BAPI dentro do contrato RAP.
Persistência que não é uma tabela simples: regras de gravação próprias, múltiplas tabelas amarradas, número de documento por number range no commit.
Migração incremental de app clássico (MVC) para o ABAP Cloud: a UI vira Fiori Elements hoje; o miolo continua nas FMs testadas — e vai sendo substituído aos poucos.
Controle total do ciclo: lock, save, cleanup e design da API nas suas mãos.
Vantagens: reuso de código testado, flexibilidade total, caminho natural de modernização brownfield — e a mesma porta de saída moderna (OData V4, Clean Core). Desvantagens: mais código para escrever e manter (handler + saver + mapeamentos), e a exigência de dominar o ciclo transacional do RAP — que é exatamente o próximo assunto.
As duas fases de uma transação RAP
Para implementar um unmanaged corretamente, é essencial entender as duas fases que todo BO RAP atravessa — e, dentro da segunda, a fronteira que separa "ainda dá para rejeitar" de "não tem mais volta".
1. Interaction phase
É a fase em que o consumidor (o app Fiori, ou um chamador EML) lê, filtra e modifica instâncias. As mudanças são mantidas no transactional buffer — um armazenamento temporário no sistema; elas ainda não estão no banco de dados. Aqui rodam os métodos do handler, uma classe local que herda de CL_ABAP_BEHAVIOR_HANDLER:
Método | Papel |
|---|---|
| Operações de modificação (buffer!) |
| Leitura de instâncias — alimenta o OData e a verificação de ETag |
| Bloqueio contra acesso concorrente (reuse o ENQUEUE legado) |
| Autorização por instância |
| Feature control dinâmico (habilitar/desabilitar operações e actions) |
| Create/read by association — entidades dependentes |
2. Save sequence — em duas metades
A save sequence dispara depois de pelo menos uma modificação bem-sucedida, quando o consumidor pede para salvar. É implementada na saver class, que herda de CL_ABAP_BEHAVIOR_SAVER — e a documentação a divide em duas metades com semânticas bem diferentes:
Fase | Método | Papel |
|---|---|---|
Early save |
| Últimos cálculos e determinações antes de persistir — é o primeiro método da fase |
| Verificação final; se falhar, a transação inteira é rejeitada (rollback) | |
Late save |
| Late numbering: as chaves definitivas são atribuídas aqui |
| Grava de fato o buffer no banco — aqui entra a BAPI/FM de persistência | |
Limpeza |
| Limpa o buffer após o save (ou após rollback) |
| Limpeza final |
O ponto de não retorno: passou do check_before_save, a transação não pode mais falhar de forma controlada. É por isso que os métodos da late save (como o save_modified das variantes managed) nem recebem o parâmetro failed — erro do consumidor não pode aparecer depois da early save; se a aplicação precisar abortar ali, o resultado é runtime error. Moral: toda validação que pode rejeitar a transação pertence à interaction phase ou ao check_before_save — nunca ao save.
E o erro que derruba todo mundo: você NÃO dá COMMIT WORK no save. Quem orquestra o commit é o framework RAP, alinhado à SAP LUW. Se a sua BAPI faz COMMIT internamente, isso quebra o contrato — por isso muitas BAPIs precisam de um wrapper antes de entrar no ABAP Cloud. Use BAPIs que aceitem controle externo de commit, ou embrulhe.
A arquitetura na prática
Um BO unmanaged é composto por três peças:
Peça | Conteúdo |
|---|---|
CDS | A interface view (modelo de dados) e a projection view (camada exposta ao serviço) |
Behavior Definition | Declara |
Behavior Pool | A classe ABAP com o handler (interaction phase) e o saver (save sequence), ambos no CCIMP (Local Types) |
Passo a passo com código
Os exemplos usam o modelo de demonstração /DMO da SAP (o mesmo do openSAP), que já traz function modules prontas para criar, alterar e salvar viagens — com buffer próprio, exatamente o cenário-alvo do unmanaged.
1. A behavior definition
Declare o cenário como unmanaged (com strict ( 2 ) — obrigatório para ABAP Cloud e recomendado sempre) e use o mapping for ... control para ligar os campos do BO à estrutura legada. A control structure (x-structure) indica quais campos foram preenchidos, imitando o padrão das BAPIs:
unmanaged implementation in class zbp_i_travel_u unique;
strict ( 2 );
define behavior for ZI_Travel_U alias Travel
late numbering
lock master
authorization master ( instance )
etag master LastChangedAt
{
create;
update;
delete;
field ( readonly ) TravelId;
mapping for /dmo/travel control /dmo/s_travel_intx
{
TravelId = travel_id;
AgencyId = agency_id;
CustomerId = customer_id;
BeginDate = begin_date;
EndDate = end_date;
Description = description;
Status = status;
LastChangedAt = lastchangedat;
}
}O bloco mapping substitui dezenas de MOVE-CORRESPONDING espalhados pelo código: o CORRESPONDING com as adições MAPPING FROM ENTITY / USING CONTROL faz a conversão nos dois sentidos, incluindo a x-structure. E o late numbering declara que a chave TravelId só nasce na save sequence — o consumidor cria com %cid, sem chave.
2. O handler — interaction phase
Cada operação vira um método no handler. É aqui que você chama a FM legada (que trabalha em buffer) e converte o resultado para as estruturas do RAP — incluindo o tratamento de erro via failed/reported, que a versão de tutorial sempre esquece:
CLASS lhc_travel DEFINITION INHERITING FROM cl_abap_behavior_handler.
PRIVATE SECTION.
METHODS create FOR MODIFY IMPORTING entities FOR CREATE Travel.
METHODS update FOR MODIFY IMPORTING entities FOR UPDATE Travel.
METHODS delete FOR MODIFY IMPORTING keys FOR DELETE Travel.
METHODS read FOR READ IMPORTING keys FOR READ Travel RESULT result.
METHODS lock FOR LOCK IMPORTING keys FOR LOCK Travel.
ENDCLASS.
CLASS lhc_travel IMPLEMENTATION.
METHOD create.
LOOP AT entities INTO DATA(entity).
DATA(ls_travel_in) = CORRESPONDING /dmo/travel( entity MAPPING FROM ENTITY ).
CALL FUNCTION '/DMO/FLIGHT_TRAVEL_CREATE'
EXPORTING is_travel = CORRESPONDING /dmo/s_travel_in( ls_travel_in )
IMPORTING es_travel = DATA(ls_travel_out)
et_messages = DATA(lt_messages).
IF lt_messages IS INITIAL.
" sucesso: devolve %cid -> chave preliminar (definitiva vem no adjust_numbers)
APPEND VALUE #( %cid = entity-%cid
TravelId = ls_travel_out-travel_id ) TO mapped-travel.
ELSE.
" erro: falha estruturada, não exceção "seca"
APPEND VALUE #( %cid = entity-%cid ) TO failed-travel.
APPEND VALUE #( %cid = entity-%cid
%msg = new_message( id = lt_messages[ 1 ]-msgid
number = lt_messages[ 1 ]-msgno
severity = if_abap_behv_message=>severity-error
v1 = lt_messages[ 1 ]-msgv1 ) )
TO reported-travel.
ENDIF.
ENDLOOP.
ENDMETHOD.
METHOD update.
LOOP AT entities INTO DATA(entity).
" USING CONTROL: preenche a x-structure a partir do %control do RAP
DATA(ls_travel) = CORRESPONDING /dmo/travel( entity MAPPING FROM ENTITY ).
DATA(ls_travelx) = CORRESPONDING /dmo/s_travel_intx( entity MAPPING FROM ENTITY USING CONTROL ).
CALL FUNCTION '/DMO/FLIGHT_TRAVEL_UPDATE'
EXPORTING is_travel = CORRESPONDING /dmo/s_travel_in( ls_travel )
is_travelx = ls_travelx
IMPORTING et_messages = DATA(lt_messages).
" ... mesmo padrão de failed/reported do create
ENDLOOP.
ENDMETHOD.
METHOD read.
LOOP AT keys INTO DATA(key).
CALL FUNCTION '/DMO/FLIGHT_TRAVEL_READ'
EXPORTING iv_travel_id = key-TravelId
IMPORTING es_travel = DATA(ls_travel)
et_messages = DATA(lt_messages).
IF lt_messages IS INITIAL.
APPEND CORRESPONDING #( ls_travel MAPPING TO ENTITY ) TO result.
ELSE.
APPEND VALUE #( %tky = key-%tky ) TO failed-travel.
ENDIF.
ENDLOOP.
ENDMETHOD.
METHOD lock.
" reusa o bloqueio legado — não invente um segundo lock
TRY.
cl_abap_lock_object_factory=>get_instance( iv_name = '/DMO/ETRAVEL'
)->enqueue( it_parameter = VALUE #( ( name = 'TRAVEL_ID'
value = REF #( keys[ 1 ]-TravelId ) ) ) ).
CATCH cx_abap_foreign_lock INTO DATA(lx_lock).
APPEND VALUE #( %tky = keys[ 1 ]-%tky ) TO failed-travel.
APPEND VALUE #( %tky = keys[ 1 ]-%tky
%msg = new_message_with_text(
severity = if_abap_behv_message=>severity-error
text = lx_lock->get_text( ) ) ) TO reported-travel.
ENDTRY.
ENDMETHOD.
ENDCLASS.Repare no padrão: na interaction phase você trabalha em buffer (as FMs /DMO mantêm o próprio) e só persiste de verdade na fase de salvamento. E cada falha vira failed + reported — é isso que a UI Fiori transforma em mensagem para o usuário.
3. O saver — save sequence
O saver herda de cl_abap_behavior_saver e redefine só o que precisa. Para reuso de FMs com late numbering, o trio típico é adjust_numbers (chave definitiva), save (persistir) e cleanup (limpar o buffer):
CLASS lsc_zi_travel_u DEFINITION INHERITING FROM cl_abap_behavior_saver.
PROTECTED SECTION.
METHODS adjust_numbers REDEFINITION.
METHODS save REDEFINITION.
METHODS cleanup REDEFINITION.
ENDCLASS.
CLASS lsc_zi_travel_u IMPLEMENTATION.
METHOD adjust_numbers.
" late numbering: troca a chave preliminar (%pid/%pre) pela definitiva
LOOP AT mapped-travel ASSIGNING FIELD-SYMBOL(<travel>).
<travel>-TravelId = get_next_travel_id( ). " number range / FM legada
ENDLOOP.
ENDMETHOD.
METHOD save.
" persiste o buffer das FMs legadas — SEM commit explícito!
CALL FUNCTION '/DMO/FLIGHT_TRAVEL_SAVE'.
ENDMETHOD.
METHOD cleanup.
" limpa o buffer das FMs após salvar ou após rollback:
" a mesma sessão ABAP pode servir outra transação RAP — buffer sujo = inconsistência
CALL FUNCTION '/DMO/FLIGHT_TRAVEL_INITIALIZE'.
ENDMETHOD.
ENDCLASS.4. Testar via EML — antes de qualquer serviço
Não espere o binding para descobrir se o BO funciona. Um classrun com EML exercita o ciclo completo (interaction phase + save sequence) em segundos:
METHOD if_oo_adt_classrun~main.
MODIFY ENTITIES OF ZI_Travel_U
ENTITY Travel
CREATE FIELDS ( AgencyId CustomerId BeginDate EndDate Description )
WITH VALUE #( ( %cid = 'create1'
AgencyId = '070001'
CustomerId = '000001'
BeginDate = cl_abap_context_info=>get_system_date( )
EndDate = cl_abap_context_info=>get_system_date( ) + 10
Description = 'Teste unmanaged via EML' ) )
MAPPED DATA(mapped) FAILED DATA(failed) REPORTED DATA(reported).
COMMIT ENTITIES. " aqui dispara a save sequence inteira
IF sy-subrc = 0.
out->write( |Criado: { mapped-travel[ 1 ]-TravelId }| ).
ENDIF.
ENDMETHOD.5. Expor o serviço
Por fim, a camada de consumo: projection view + projection behavior definition (use create; use update; use delete;), service definition e service binding — OData V4 UI para um app Fiori Elements, ou OData V4 Web API para consumo sistema-a-sistema (o passo a passo completo dessa segunda rota está no post de Web API do hub). A partir daí, o consumidor nem sabe que por baixo tem FM de 2008 — e é exatamente essa a graça.
Boas práticas e erros comuns
Nunca dê
COMMIT WORKno saver: o RAP controla a transação. BAPI com commit interno = wrapper antes de entrar.Validação que rejeita = early save (ou interaction phase). Depois do
check_before_savenão há falha controlada — é o ponto de não retorno.Use o
mapping ... control:CORRESPONDING ... MAPPING FROM ENTITY USING CONTROLelimina a conversão manual e mantém a compatibilidade com as x-structures das BAPIs.Separe interação de salvamento: nos handlers, buffer; no
save, persistência. FM que grava direto no handler quebra o modelo.Reaproveite o lock existente: se a FM já bloqueia via ENQUEUE, use o mesmo objeto de bloqueio no método
lock— dois locks para o mesmo dado é receita de deadlock e de "objeto bloqueado por mim mesmo".Trate erros em
failed/reported: devolva falhas estruturadas para a UI, em vez de levantar exceções "secas" que viram short dump na cara do usuário.Sempre
strict ( 2 ): os checks adicionais do modo estrito são obrigatórios no ABAP Cloud e pegam erro de contrato em tempo de ativação.Limpe o buffer no
cleanupreligiosamente: a mesma sessão pode atender outra transação RAP — buffer sujo de uma transação anterior é o bug intermitente mais difícil de reproduzir que existe.Antes de partir para o unmanaged completo, confira o espectro: se só a gravação é legada,
managed with unmanaged saveresolve com uma fração do código.
Perguntas frequentes
Qual a diferença essencial entre managed e unmanaged?
No managed, o framework implementa o buffer, as operações e a persistência por você. No unmanaged, você implementa tudo isso no behavior pool (handler + saver), normalmente reusando BAPIs/FMs — que costumam trazer o próprio buffer.
E o "managed with unmanaged save"? Não é a mesma coisa?
Não — e a diferença economiza semanas. Nele, a interaction phase inteira (buffer, CRUD, draft) continua com o framework; só a gravação é sua, no save_modified, recebendo as instâncias em TYPE REQUEST FOR CHANGE/DELETE. É o sabor certo quando a tabela/BAPI de destino é legada mas o ciclo transacional pode ser novo. O unmanaged completo só se justifica quando o buffer também é legado.
Posso converter um BO unmanaged em managed depois?
Sim, mas é uma reescrita: o managed espera persistência gerenciada pelo framework. O caminho natural é migrar quando a lógica legada for finalmente substituída por tabela e regras próprias do RAP — e as variantes intermediárias (additional/unmanaged save) servem de degrau.
Preciso implementar todos os métodos da save sequence?
Não. Você só redefine o que precisa. Para reuso de FMs, normalmente bastam save e cleanup; adjust_numbers entra com late numbering, e finalize/check_before_save conforme houver determinações e validações finais.
Por que minha validação no save gera runtime error em vez de mensagem?
Porque a late save phase não aceita falha controlada — o parâmetro failed nem existe ali. Validação que pode rejeitar a transação pertence à interaction phase ou, no limite, ao check_before_save (early save).
Dá para usar unmanaged no ABAP Cloud (S/4HANA Cloud)?
Sim, desde que tudo que você chama seja liberado para ABAP Cloud. BAPIs clássicas não liberadas precisam de um wrapper liberado antes de entrar no behavior pool.
Preciso de draft no unmanaged?
Draft é opcional e exige implementação adicional (tabela de draft e mapeamento). Para Web APIs ou apps de transação direta, você pode dispensá-lo — e, se draft for requisito forte, é mais um ponto a favor de avaliar o managed with unmanaged save, onde o draft vem do framework.
O unmanaged serve para Web API também?
Perfeitamente — a "casca" é a mesma. A projection + service definition + binding Web API expõem o BO unmanaged como endpoint OData V4 igual a um managed; o consumidor não vê diferença nenhuma.
Conclusão
O unmanaged scenario é a ponte entre o mundo clássico e o ABAP Cloud: ele deixa você expor lógica que já existe — BAPIs, FMs e tabelas com regras próprias — através de um serviço RAP moderno e Clean Core. O custo é escrever mais código (handler + saver) e dominar as duas fases da transação, com o ponto de não retorno da save sequence sempre no radar; o benefício é controle total e reaproveitamento do que já está testado. E a régua de decisão ficou mais fina do que "managed ou unmanaged": percorra o espectro — managed, additional save, unmanaged save, unmanaged — e pare no primeiro sabor que resolve. Comece pelo managed sempre que puder; traga o unmanaged quando houver algo que valha a pena reutilizar.
Referências oficiais
Comentários 0
Ainda sem comentários
Seja o primeiro a comentar este artigo.
Entre na conversa
Faça login para deixar seu comentário neste artigo.