Construindo uma Web API com RAP
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:
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.
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.
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' " queryE, 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 | Persistência do log de processamento (auditoria + idempotência) |
2 | Root view entity | O RAP BO sobre o log |
3 | Abstract entities | Modelagem do payload do POST (request hierárquico + response) |
4 | Abstract BDEF | 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+seqcomo 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 desc3. 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 ( | Function ( | |
|---|---|---|
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 |
|
|
" 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) |
|
Idempotência em reenvio |
|
Auditoria de cada tentativa | Log como recurso persistido, |
Consulta de status pelo parceiro/suporte | GET no entity set com |
Contrato imutável de auditoria |
|
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
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.
Payload hierárquico = abstract entities + abstract BDEF
with hierarchy. Nada de campos string com JSON dentro — o contrato tipado é o ganho do RAP.V4 desde o início quando há parâmetro deep — o V2 barra no binding.
Binding Web API, não UI, para consumo A2X: contrato enxuto, sem bagagem de anotações de tela.
readonly : updateem tudo que é auditoria — log que aceita PATCH não é log.Guarde o hash do payload e desenhe a idempotência antes do primeiro parceiro reenviar (porque ele vai reenviar).
Projection é contrato: exponha o mínimo; cada
usea mais é superfície de API para sempre.Teste a action via EML antes do HTTP — separa erro de lógica de erro de exposição em segundos.
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
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.