Blog SAPienteSAPiente
RAPOData

Construindo uma Web API com RAP

SAPiente7 de jul. de 2026· 10 min read

Web API não é Fiori sem tela

Antes da relseased do RAP, quando precisavamos expor as informações do SAP para ser consumidor externamente existia ou ainda existem dois caminhos, SEGW ou via classe com a CL_RESTHTTP_HANDLER, porém a implementação manual era mais trabalhosa, com RAP e tudo muito mais rápido e fluído, entretanto, quando o requisito é "expor uma API para o sistema externo mandar documentos", muita gente pega o mesmo caminho do app Fiori — CDS, behavior, service binding UI — e só troca o binding no final. Funciona? Às vezes. Mas uma API de integração de verdade tem exigências que um CRUD de tela não tem: payload complexo e hierárquico chegando num POST, resposta estruturada com status e mensagens, idempotência (o parceiro reenviou? não pode duplicar), auditoria (o que chegou, quando, deu certo?) e consulta de status para o suporte.

Este post monta essa arquitetura completa com RAP — do define table ao POST — usando um cenário de exemplo: um portal externo que envia pedidos para o S/4HANA. O desenho tem três ideias centrais:

  1. O recurso persistido da API é um log de processamento — não o pedido em si. Cada requisição que chega vira uma linha auditável.

  2. A porta de entrada é uma static action com parâmetro deep — o payload hierárquico inteiro (cabeçalho + parceiros + itens + programações) chega num único POST tipado.

  3. A resposta é uma abstract entity — número do documento gerado, status e mensagens, no formato que o consumidor precisa.

Vamos focar nos artefatos RAP; a lógica de negócio dentro do behavior pool (orquestração, BAPIs, validações) fica para outro post — aqui o assunto é o contrato e a estrutura.

Um único RAP entrega CRUD e operações — juntos

Vale esclarecer desde já, porque é fonte comum de confusão: uma Web API RAP não obriga a escolher entre expor o CRUD do objeto ou expor operações de processo. É o mesmo business object, o mesmo serviço, o mesmo endpoint — e ele oferece as duas coisas ao mesmo tempo. O que você decide é quais capacidades habilitar, não um modelo em detrimento do outro.

Um BO managed já entrega o CRUD de fábrica: declarou create; update; delete; na BDEF e expôs com use create; use update; use delete; na projection, o consumidor ganha POST, PATCH, DELETE e GET direto no entity set — inclusive deep create (cabeçalho + filhas num request só) quando o BO tem composições, cortesia do OData V4:

POST .../OrderIntake

{
  "ExternalId": "PORTAL-2026-000123",
  "OperationType": "C",
  "Status": "P"
}
" CRUD padrão, de graça com o managed:
PATCH  .../OrderIntake(ExternalId='PORTAL-2026-000123',Seq='000001')   " update
DELETE .../OrderIntake(ExternalId='PORTAL-2026-000123',Seq='000001')   " delete
GET    .../OrderIntake?$filter=Status eq 'E'                           " query

E, no mesmo BO, você adiciona as operações de negócio — a processOrder deste post, uma simulateFreight, o que o processo pedir. O serviço final expõe tudo lado a lado: o consumidor faz CRUD nas instâncias e chama actions/functions no mesmo endpoint. CRUD e operações não competem; convivem.

Este post foca justamente a parte que os tutoriais de CRUD não cobrem: quando o request é um processo (dispara BAPIs, cria documentos standard, precisa de idempotência e auditoria), a peça central passa a ser uma static action com payload profundo, e o recurso persistido vira um log auditável — com o CRUD desse log disponível na mesma API, para consulta de status. Não é um modelo alternativo ao CRUD; é o CRUD mais a camada de processo, no mesmo RAP.

A arquitetura em uma tabela

#

Artefato

Papel na API

1

Tabela transparente zsd_order_log

Persistência do log de processamento (auditoria + idempotência)

2

Root view entity ZSD_R_OrderLogTP

O RAP BO sobre o log

3

Abstract entities ZSD_D_*

Modelagem do payload do POST (request hierárquico + response)

4

Abstract BDEF with hierarchy

Amarra a árvore do payload num tipo deep

5

BDEF managed + static action

Comportamento do BO + a porta de entrada da API

6

Projection + BDEF de projeção

A camada de consumo (o que a API expõe)

7

Service Definition

O escopo do serviço

8

Service Binding OData V4 – Web API

O endpoint

Passo 1 — A tabela de log: o coração silencioso da API

Antes de qualquer CDS, a decisão de arquitetura: o que a API persiste? Neste desenho, um log de processamento, mas que também pode fazer o CRUD de um processo customizado do cliente. Neste exemplo por exigência do négocio as operações seriam definidas pelo payload, cada requisição do portal vira um registro com a identificação externa, uma sequência, o resultado e um hash do payload:

@EndUserText.label : 'Log de processamento - Pedidos do Portal'
@AbapCatalog.enhancement.category : #NOT_EXTENSIBLE
@AbapCatalog.tableCategory : #TRANSPARENT
@AbapCatalog.deliveryClass : #A
@AbapCatalog.dataMaintenance : #RESTRICTED
define table zsd_order_log {

  key client        : abap.clnt not null;
  key external_id   : abap.char(25) not null;   " ID do pedido no portal
  key seq           : abap.numc(6) not null;    " sequência
  operation_type    : abap.char(1);             " C=criar M=modificar E=excluir
  sales_order       : abap.char(10);            " documento gerado no S/4
  status            : abap.char(1);             " S/E/P
  msgid             : abap.char(20);
  msgno             : abap.numc(3);
  message           : abap.char(220);
  payload_hash      : abap.char(64);            " SHA do payload
  created_at        : timestampl;
  created_by        : abap.char(12);

}

Repare em dois campos que fazem a API ser "de gente grande":

  • external_id + seq como chave: o mesmo pedido do portal pode gerar N tentativas de processamento — cada uma auditada, nenhuma sobrescrita.

  • payload_hash: o hash do payload recebido permite detectar reenvio idêntico (retry do parceiro) e responder de forma idempotente sem reprocessar.

Passo 2 — O RAP BO sobre o log

A root view entity é direta — é um espelho da tabela com os nomes em CamelCase e a semântica de auditoria anotada:

@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'Root - Log de Pedidos do Portal'
@Metadata.ignorePropagatedAnnotations: true
@ObjectModel.usageType:{
    serviceQuality: #D,
    sizeCategory: #S,
    dataClass: #TRANSACTIONAL }
define root view entity ZSD_R_OrderLogTP
  as select from zsd_order_log
{
  key external_id    as ExternalId,
  key seq            as Seq,
      operation_type as OperationType,
      sales_order    as SalesOrder,
      status         as Status,
      msgid          as Msgid,
      msgno          as Msgno,
      message        as Message,
      @Semantics.systemDateTime.createdAt: true
      created_at     as CreatedAt,
      @Semantics.user.createdBy: true
      created_by     as CreatedBy
}

Passo 3 — O payload do POST: abstract entities em árvore

Aqui mora a parte mais interessante — e menos documentada — do desenho. O pedido que chega do portal é hierárquico: cabeçalho, parceiros, itens, e cada item com suas divisões de remessa. Em RAP, esse payload é modelado com abstract entities compostas: uma root abstract entity com composition para as filhas. (Se abstract entity é novidade, o guia básico está no hub.)

A raiz do request:

@EndUserText.label: 'Request - Pedido do Portal'
define root abstract entity ZSD_D_OrderRequest
{
  key ExternalId    : abap.char(25);
  OperationType     : abap.char(1); 
  CustomerRef       : abap.char(35);
  RequestedDate     : abap.dats;
  SalesOrg          : abap.char(4);
  DistChannel       : abap.char(2);
  Division          : abap.char(2);
  _Partners         : composition [0..*] of ZSD_D_OrderPartner;
  _Items            : composition [0..*] of ZSD_D_OrderItem;
  _Schedules        : composition [0..*] of ZSD_D_OrderSchedule;
}

As filhas, cada uma com a association to parent amarrando a árvore:

@EndUserText.label: 'Request - Parceiro do Pedido'
define abstract entity ZSD_D_OrderPartner
{
  key ExternalId  : abap.char(25);
  key PartnerRole : abap.char(2);
  PartnerCode     : abap.char(10);
  _Parent         : association to parent ZSD_D_OrderRequest
                    on $projection.ExternalId = _Parent.ExternalId;
}

@EndUserText.label: 'Request - Item do Pedido'
define abstract entity ZSD_D_OrderItem
{
  key ExternalId : abap.char(25);
  key ItemNumber : abap.char(6);
  Material       : abap.char(40);
  Quantity       : abap.dec(13,3);
  Unit           : abap.char(3);
  _Parent        : association to parent ZSD_D_OrderRequest
                   on $projection.ExternalId = _Parent.ExternalId;
  _Splits        : composition [0..*] of ZSD_D_OrderItemSplit;
}

@EndUserText.label: 'Request - Divisão de remessa do item'
define abstract entity ZSD_D_OrderItemSplit
{
  key ExternalId : abap.char(25);
  key ItemNumber : abap.char(6);
  key Plant      : abap.char(4);
  SplitQuantity  : abap.dec(13,3);
  StorageLoc     : abap.char(4);
  Batch          : abap.char(10);
  _Parent        : association to parent ZSD_D_OrderItem
                   on  $projection.ExternalId = _Parent.ExternalId
                   and $projection.ItemNumber = _Parent.ItemNumber;
}

@EndUserText.label: 'Request - Programação de entrega'
define abstract entity ZSD_D_OrderSchedule
{
  key ExternalId   : abap.char(25);
  key ScheduleLine : abap.char(4);
  DeliveryDate     : abap.dats;
  DeliveryWindow   : abap.char(2);
  _Parent          : association to parent ZSD_D_OrderRequest
                     on $projection.ExternalId = _Parent.ExternalId;
}

E a response — o que o portal recebe de volta:

@EndUserText.label: 'Response - Resultado do processamento'
define abstract entity ZSD_D_OrderResponse
{
  SalesOrder : abap.char(10);
  Status     : abap.char(1);
  Messages   : abap.char(255);
}

Três níveis de profundidade (Request → Item → Split), quatro composições — e tudo isso vai chegar tipado na implementação, sem parse manual de JSON. É o framework trabalhando para você.

Passo 4 — A abstract BDEF: transformando a árvore num tipo deep

Abstract entities compostas só viram um parâmetro deep utilizável quando uma abstract behavior definition declara a hierarquia. É uma BDEF de implementação abstract, com with hierarchy, listando as associações de cada nó:

abstract;
strict ( 2 );
with hierarchy;

define behavior for ZSD_D_OrderRequest alias OrderRequest
{
  association _Partners { }
  association _Items { }
  association _Schedules { }
}

define behavior for ZSD_D_OrderPartner alias OrderPartner
{
}

define behavior for ZSD_D_OrderItem alias OrderItem
{
  association _Splits { }
}

define behavior for ZSD_D_OrderItemSplit alias OrderItemSplit
{
}

define behavior for ZSD_D_OrderSchedule alias OrderSchedule
{
}

Sem transactional behavior nenhum — abstract BDEF é puro mecanismo de tipagem. Ela existe para o compilador saber que ZSD_D_OrderRequest não é uma estrutura plana, e sim uma árvore.

Passo 5 — A BDEF managed: o BO e a porta de entrada

Agora o comportamento do BO de log — e a estrela do desenho, a static action com parâmetro deep:

managed implementation in class zbp_sd_orderlogtp unique;
strict ( 2 );

define behavior for ZSD_R_OrderLogTP alias OrderLog
persistent table zsd_order_log
lock master
authorization master ( instance )
{
  create;
  update;
  delete;

  field ( readonly : update ) ExternalId, SalesOrder, Seq;

  static action processOrder
    deep parameter ZSD_D_OrderRequest
    result [1] ZSD_D_OrderResponse;

  mapping for zsd_order_log
    {
      ExternalId    = external_id;
      Seq           = seq;
      OperationType = operation_type;
      SalesOrder    = sales_order;
      Status        = status;
      Msgid         = msgid;
      Msgno         = msgno;
      Message       = message;
      CreatedAt     = created_at;
      CreatedBy     = created_by;
    }
}

Cada linha dessa BDEF é uma decisão de API:

  • static action: a operação de negócio não pertence a nenhuma instância do log — ela cria a instância. Static é o vínculo correto. (Temos um guia completo de tipos de action.)

  • deep parameter ZSD_D_OrderRequest: o POST inteiro — cabeçalho, parceiros, itens, splits, programações — entra num único parâmetro tipado. Na implementação, você navega na estrutura como quem navega numa tabela interna aninhada.

  • result [1] ZSD_D_OrderResponse: o contrato de saída, tão tipado quanto o de entrada.

  • field ( readonly : update ): chave e documento gerado ficam imutáveis após a criação — ninguém "corrige" um log de auditoria via PATCH.

Parâmetro deep exige OData V4. O service binding V2 rejeita operações não-standard com parâmetro deep — o erro aparece já na ativação do binding. Se o payload da sua API é hierárquico (e de integração quase sempre é), a decisão V4 está tomada antes de você perceber.

Passo 6 — A projection: a cara pública do BO

A camada de consumo define o que a API expõe — e o provider contract transactional_query declara o contrato transacional da projeção:

@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'Consumption - API Pedidos do Portal'
@Metadata.ignorePropagatedAnnotations: true
@ObjectModel.usageType:{
    serviceQuality: #D,
    sizeCategory: #S,
    dataClass: #TRANSACTIONAL }
define root view entity ZSD_C_OrderLog
  provider contract transactional_query
  as projection on ZSD_R_OrderLogTP
{
  key ExternalId,
  key Seq,
      OperationType,
      SalesOrder,
      Status,
      Msgid,
      Msgno,
      Message,
      CreatedAt,
      CreatedBy
}

E a BDEF de projeção reusa só o que a API deve oferecer:

projection;
strict ( 2 );

define behavior for ZSD_C_OrderLog alias OrderLogApi
{
  use create;
  use update;
  use delete;

  use action processOrder;
}

É o use action processOrder que torna a porta de entrada visível no serviço. O que não estiver aqui, não existe para o consumidor — a projection é o seu firewall de contrato.

Passo 7 — Service Definition: o escopo

@EndUserText.label: 'Service Definition - API Pedidos do Portal'
define service ZSD_API_ORDER {
  expose ZSD_C_OrderLog as OrderIntake;
}

O alias as OrderIntake vira o nome do entity set na URL — capriche: ele é parte do contrato público e aparece em cada chamada do parceiro.

Passo 8 — Service Binding: OData V4 – Web API

No ADT: botão direito na Service Definition → New Service Binding → binding type OData V4 – Web API → ativar → Publish. E aqui vale entender a diferença que dá nome ao post:

Binding type

Para quê

O que muda

OData V4 – UI

Apps Fiori Elements

Metadata carrega as anotações de UI; preview de app disponível

OData V4 – Web API

Consumo sistema-a-sistema (A2X)

Serviço enxuto, sem semântica de UI — é um endpoint, não uma tela

Publicado, o serviço responde em algo como:

/sap/opu/odata4/sap/zsd_api_order/srvd/sap/zsd_api_order/0001/

Caso o o publish não funcione via eclipse(o que quase sempre acontece para OData V4), você deve ativar manualmente seu serviço, você pode seguir o passo a passo neste outro post de Como Ativar um API Odata V4.

Para consumo externo, resta a camada de acesso: no S/4HANA Cloud / BTP ABAP Environment, um Communication Scenario custom referenciando o serviço + Communication Arrangement com o usuário técnico (inbound); no on-premise, a ativação do serviço ICF e a autenticação (Basic/OAuth) conforme a política da casa.

Como o consumidor usa: as chamadas da API

1. Enviar um pedido (a static action via POST)

Em OData V4, a static action é invocada no entity set, e o corpo é o parâmetro deep serializado — as composições viram arrays aninhados com o nome das associações (exemplo ilustrativo):

POST .../OrderIntake/SAP__self.processOrder

{
  "ExternalId": "PORTAL-2026-000123",
  "OperationType": "C",
  "CustomerRef": "PEDIDO-WEB-98765",
  "RequestedDate": "2026-07-15",
  "SalesOrg": "1000",
  "DistChannel": "10",
  "Division": "00",
  "_Partners": [
    { "PartnerRole": "AG", "PartnerCode": "0000100042" },
    { "PartnerRole": "WE", "PartnerCode": "0000100077" }
  ],
  "_Items": [
    {
      "ItemNumber": "000010",
      "Material": "MAT-4711",
      "Quantity": 100.000,
      "Unit": "UN",
      "_Splits": [
        { "Plant": "1010", "SplitQuantity": 60.000, "StorageLoc": "0001" },
        { "Plant": "1020", "SplitQuantity": 40.000, "StorageLoc": "0001" }
      ]
    }
  ],
  "_Schedules": [
    { "ScheduleLine": "0001", "DeliveryDate": "2026-07-20", "DeliveryWindow": "AM" }
  ]
}

E a resposta, tipada pela ZSD_D_OrderResponse:

{
  "SalesOrder": "0000054321",
  "Status": "S",
  "Messages": "Pedido criado com sucesso"
}

Afinal, o que é esse SAP__self na URL?

É a dúvida de todo mundo no primeiro Postman — e a causa de muito 404 misterioso. A explicação tem duas metades:

Metade 1 — a regra do OData V4: na especificação, actions e functions bound (vinculadas a uma entidade ou entity set) são invocadas pelo nome qualificado: Namespace.Operacao ou Alias.Operacao. Diferente do V2, onde function imports viviam soltos na raiz do serviço, no V4 a operação pertence a um schema — e a URL precisa dizer de qual.

Metade 2 — o que o RAP faz: no $metadata de um serviço OData V4 gerado pelo RAP, o schema do próprio serviço tem um namespace longo e técnico (no padrão com.sap.gateway.srvd.<service_definition>.v0001) — e, para ninguém precisar digitar isso, recebe um alias fixo: SAP__self. Leia como "o schema deste próprio serviço" (self). Logo:

POST .../OrderIntake/SAP__self.processOrder
"        └─ entity set  └─ alias do schema . nome da action

" ou seja: "execute a action processOrder, definida NO SCHEMA DESTE SERVIÇO,
"          vinculada ao entity set OrderIntake"

Você encontra o alias no próprio $metadata (atributo Alias do elemento Schema) — e ele reaparece em outros cantos do ecossistema V4, como nas local annotations do Fiori Elements (annotate SAP__self.MinhaEntidade with ...). O duplo underscore é convenção de nome reservado da SAP: no metadata você verá parentes como SAP__common e SAP__capabilities, que são aliases dos vocabulários de anotação — esses não qualificam actions, só anotações.

O erro clássico: chamar POST .../OrderIntake/processOrder — sem o qualificador. O serviço não encontra a operação e devolve erro, e o desenvolvedor perde horas conferindo BDEF, projection e binding que estão perfeitos. Se a action existe no $metadata mas o POST falha, confira o SAP__self. antes de qualquer outra coisa.

2. Consultar o status (GET no log)

De graça, porque o BO de log está exposto: o parceiro (ou o suporte) consulta o histórico de processamento com o query power do OData V4:

GET .../OrderIntake?$filter=ExternalId eq 'PORTAL-2026-000123'&$orderby=Seq desc

3. Executar uma function (GET) — o par de leitura da action

Se a sua API também precisa responder algo sem gravar nada — simular, validar, consultar um cálculo — o artefato é uma function, não uma action (o guia completo está no post de Functions). Na Web API isso brilha: sem UI no meio, é GET puro. Digamos que o serviço exponha uma simulateFreight que estima o frete de um pedido hipotético antes do envio de verdade.

A diferença de invocação em relação à action processOrder é o que separa os dois mundos do OData V4:

Action (processOrder)

Function (simulateFreight)

Verbo HTTP

POST (tem efeito colateral)

GET (sem efeito colateral)

Parâmetros

No corpo (body JSON)

Inline na URL, entre parênteses

Qualificador de schema

SAP__self.

SAP__self. (igual)

" function ESTÁTICA (sem instância) — parênteses vazios são obrigatórios:
GET .../OrderIntake/SAP__self.calculatePendingTotal()

" function de INSTÂNCIA com parâmetro — chave da instância + parâmetro inline:
GET .../OrderIntake(ExternalId='PORTAL-2026-000123',Seq='000001')/SAP__self.simulateFreight(Carrier='JADLOG')

Como é GET, o retorno ainda aceita a composição do OData V4 — $select para trazer só parte do resultado, por exemplo. E como toda function, ela não tem side effects nem state messages: numa Web API isso raramente importa (não há tela para refrescar), mas reforça a regra — function devolve, nunca muda. Se o consumidor precisa localizar a instância por chave de negócio em vez do UUID técnico, o caminho é uma key function ou uma static function que recebe a chave semântica como parâmetro (ambas no post de Functions).

4. Testar pelo próprio ADT

Antes de qualquer Postman: o service binding tem o botão de preview do serviço, e um teste de mesa via EML no próprio sistema valida a action sem HTTP no meio:

MODIFY ENTITIES OF ZSD_R_OrderLogTP
  ENTITY OrderLog
    EXECUTE processOrder FROM VALUE #(
      ( %cid   = 'test1'
        %param = VALUE #( ExternalId  = 'TESTE-001'
                          OperationType = 'C'
                          _Items = VALUE #( ( ItemNumber = '000010'
                                              Material   = 'MAT-4711'
                                              Quantity   = 10 ) ) ) ) )
  RESULT DATA(response)
  FAILED DATA(failed) REPORTED DATA(reported).
COMMIT ENTITIES.

Por que a camada de processo, além do CRUD?

Exigência de integração

Como o desenho resolve

Payload hierárquico num único request

Static action + deep parameter (abstract entities em árvore)

Resposta estruturada (documento + status + mensagens)

result [1] tipado por abstract entity

Idempotência em reenvio

payload_hash no log + chave ExternalId

Auditoria de cada tentativa

Log como recurso persistido, Seq via late numbering

Consulta de status pelo parceiro/suporte

GET no entity set com $filter/$orderby

Contrato imutável de auditoria

field ( readonly : update ) nas chaves e no documento

Evolução sem quebrar consumidor

Projection como camada de contrato + alias no service definition

O CRUD do BO cobre a manipulação direta das instâncias — e continua ali, disponível. Mas quando o request dispara a criação de documentos standard via processo, um POST cru sobre "pedido" empurraria o parceiro a orquestrar N chamadas (cria cabeçalho, cria itens, cria programações...) contra objetos que nem pertencem ao BO, com transacionalidade frágil. A static action resolve isso: um POST = uma transação = uma linha de auditoria. É REST de negócio, não REST de tabela — e uma coisa não exclui a outra no mesmo serviço.

Boas práticas consolidadas

  1. CRUD e operações no mesmo BO. Não escolha entre expor o CRUD do objeto ou as operações de processo — o RAP entrega os dois no mesmo serviço. Habilite o CRUD que fizer sentido e adicione as actions/functions que o processo pedir.

  2. Payload hierárquico = abstract entities + abstract BDEF with hierarchy. Nada de campos string com JSON dentro — o contrato tipado é o ganho do RAP.

  3. V4 desde o início quando há parâmetro deep — o V2 barra no binding.

  4. Binding Web API, não UI, para consumo A2X: contrato enxuto, sem bagagem de anotações de tela.

  5. readonly : update em tudo que é auditoria — log que aceita PATCH não é log.

  6. Guarde o hash do payload e desenhe a idempotência antes do primeiro parceiro reenviar (porque ele vai reenviar).

  7. Projection é contrato: exponha o mínimo; cada use a mais é superfície de API para sempre.

  8. Teste a action via EML antes do HTTP — separa erro de lógica de erro de exposição em segundos.

  9. Versione pensando no parceiro: mudou o request? Nova versão do serviço/alias, não mutação silenciosa da abstract entity.

Perguntas frequentes

Uma Web API RAP pode ser só CRUD, sem action nenhuma?

Claro. Se o consumidor só precisa manipular as instâncias do objeto (cadastro custom, tabela de parametrização, documento próprio), basta o BO managed com create; update; delete;, a projection com os use e o binding Web API: POST, PATCH, DELETE e GET (com deep create via composições no V4) saem de fábrica. As actions e functions são adições a esse mesmo BO — você as inclui quando o request também precisa disparar um processo, sem nunca abrir mão do CRUD que já está lá.

Por que uma static action em vez do create padrão do BO?

Porque o create do BO cria uma instância da entidade exposta (uma linha de log) — e o que o parceiro envia é um pedido de negócio hierárquico que dispara um processo. A static action com deep parameter recebe o processo inteiro num POST; o create do log acontece por dentro, como consequência.

Preciso mesmo da abstract behavior definition?

Para parâmetro deep, sim — é ela (implementação abstract, with hierarchy) que transforma a árvore de abstract entities num tipo hierárquico. Para parâmetros flat, a abstract entity sozinha resolve.

Web API funciona com OData V2?

Existe binding V2 – Web API, mas com a restrição decisiva: operações com parâmetro deep não são suportadas em V2 — o service binding acusa erro. API de integração com payload hierárquico é território V4.

Como fica a autenticação do consumidor externo?

No S/4HANA Cloud / BTP ABAP Environment: Communication Scenario custom (inbound) referenciando o serviço + Communication Arrangement com usuário de comunicação. No on-premise: ativação ICF do serviço e o mecanismo de autenticação da política local (Basic, certificado, OAuth). O RAP entrega o endpoint; a porta de segurança é configuração.

E se o parceiro reenviar o mesmo pedido duas vezes?

É para isso que o log guarda ExternalId e payload_hash: na implementação da action, hash igual para o mesmo ID externo = reenvio → responda o resultado já processado (idempotência) em vez de duplicar o documento. O desenho da persistência é metade da solução.

Posso expor o mesmo BO como UI e como Web API?

Pode — e é elegante: duas projections (uma com anotações de UI para o app de monitoramento do log, outra enxuta para a API), cada uma com sua service definition e binding. Mesmo BO, dois contratos.

Conclusão

Uma Web API de integração com RAP é mais desenho do que código. E o primeiro entendimento a firmar é que não há escolha excludente: o CRUD do objeto (POST/PATCH/DELETE/GET de fábrica no managed) e as operações de processo (actions e functions) convivem no mesmo BO, no mesmo endpoint. Quando o request dispara um processo — BAPIs, documentos standard, idempotência, auditoria — as peças deste post entram em cena: a tabela de log como recurso auditável, as abstract entities como contrato hierárquico do payload, a abstract BDEF dando o tipo deep, a static action como porta de entrada transacional, a projection como firewall de contrato e o binding V4 Web API como endpoint. O resultado é uma API idempotente, auditável e tipada de ponta a ponta, sem uma linha de parse de JSON — com o CRUD ali do lado quando o consumidor precisar dele. O padrão está aí; troque o domínio e use.

Referências oficiais

TagsADTActionsBDEFBO InterfaceClean CoreDesenvolvedorEML
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.