CDS Aspects: como reutilizar campos, cálculos e anotações entre views
O fim do copy-paste de fórmulas entre views
Toda base CDS grande sofre do mesmo mal: a mesma fórmula — uma classificação ABC, um cálculo de idade, um bloco de anotações analíticas — copiada e colada em dezenas de views. Aí o threshold muda, alguém atualiza três views e esquece a quarta, e os relatórios começam a divergir sutilmente.
CDS Aspects resolvem isso. Um aspect é um artefato de reuso do ABAP CDS, definido com DEFINE ASPECT, que encapsula campos, cálculos e anotações repetidos num único objeto — uma single source of truth. As views consomem o aspect via BIND ASPECT e INCLUDE, e mudar a regra em um lugar reflete em todas elas.
Este guia mostra o que é um CDS Aspect, quando (e quando não) usá-lo, a sintaxe de definição e consumo, e exemplos prontos — classificação de clientes, cálculo de idade e padronização de anotações para analytics.
O essencial em 4 linhas
Um CDS Aspect agrupa campos, cálculos e anotações reutilizáveis — definido com
DEFINE ASPECT.Ele não gera view SQL e não pode ser consultado em
SELECT; é consumido viaBIND ASPECT+INCLUDE.Funciona em view entities modernas — não em views DDIC clássicas (
DEFINE VIEW) nem direto em OData/ABAP SQL.Use quando o mesmo cálculo aparece em 3+ views; não use para lógica trivial ou de uma view só.
Não confunda os dois "aspects" do ABAP CDS: este post trata do aspect de reuso (campos/anotações). Existe também um artefato diferente, também chamado "aspect", usado em access control (o DEFINE ASPECT dentro de DEFINE ACCESSPOLICY, ligado ao pfcg_auth) — esse é assunto do guia de CDS Access Control (DCL). Mesmo nome, propósitos opostos.
O que é um CDS Aspect?
Um CDS Aspect é um artefato de reuso em tempo de modelagem — ele não tem persistência própria nem representação SQL no banco. Pense nele como um "bloco" de elementos calculados e anotações que você define uma vez e injeta em várias view entities. Ele resolve o problema clássico de duplicação de fórmulas: quando você percebe que está copiando o mesmo CASE de view em view, é hora de extrair para um aspect.
Por não ser uma data source, um aspect não pode aparecer num FROM e não compila em SELECT FROM aspect. Quem precisa de uma fonte consultável usa uma view; o aspect entra dentro dessa view.
Quando usar (e quando não usar) um aspect?
A regra prática para extrair um aspect é a duplicação: se o mesmo cálculo ou bloco de anotações aparece em três ou mais views, vale a indireção. Abaixo disso, costuma não compensar o overhead de criar o aspect, fazer o bind e o include.
Bons casos de uso:
Cálculos repetidos em várias views (KPIs, conversões, classificações de negócio como faixas ABC ou etárias).
Blocos de anotações reutilizáveis — semânticas de moeda/quantidade ou anotações analíticas para SAC e Analytics for RAP.
Arquiteturas CDS grandes onde a manutenção centralizada é crítica: mudar a regra num lugar deve refletir em N views.
Quando evitar:
Só 1 ou 2 views consomem a lógica — mantenha o cálculo direto na view.
A lógica envolve loops, exceções ou estado — aspect só aceita expressões SQL declarativas. Para procedural, use AMDP ou CDS Scalar Function.
Você precisa de joins ou associations complexas — isso pertence à view, não ao aspect (que não tem
FROM).A lógica é específica de uma única view — não generalize por estética.
Anti-padrão "kitchen sink": um único aspect com 30 campos misturando moeda, datas, classificações e formatação. Quebre em aspects coesos (Z_CURRENCY_ASPECT, Z_DATE_FORMAT_ASPECT), cada um com uma responsabilidade clara.
Como definir um aspect (DEFINE ASPECT)?
A definição declara os campos de entrada (tipados) e os elementos calculados que os usam. O esqueleto é simples:
[@aspect_annotations]
define aspect aspect_name
{
field1 : type1; // campo de entrada
field2 : type2;
[@field_annotation]
calculated_field : <expression>; // elemento calculado
}Veja um exemplo real: uma classificação ABC de clientes que várias views analíticas precisam — em vez de repetir o CASE em cada uma, encapsule no aspect:
@EndUserText.label: 'Customer ABC Classification'
define aspect Z_CUSTOMER_ABC_CLASSIFICATION
{
total_revenue : abap.curr( 15, 2 );
@EndUserText.label: 'ABC Class'
case
when total_revenue >= 1000000 then 'A'
when total_revenue >= 250000 then 'B'
else 'C'
end as ABCClass,
@EndUserText.label: 'Strategic Customer Flag'
case
when total_revenue >= 1000000 then cast( 'X' as abap.char( 1 ) )
else cast( '' as abap.char( 1 ) )
end as IsStrategic
}Como consumir um aspect (BIND + INCLUDE)?
O consumo tem duas partes. Primeiro o bind aspect mapeia os campos de entrada do aspect para os elementos da view, usando o operador =>. Depois, na lista de elementos, o include traz os campos calculados. As cláusulas-chave:
bind aspect <nome> ( campo_entrada => $projection.campo_local, ... )— mapeia as entradas.include <nome>.*— traz todos os campos do aspect;include <nome>.campotraz um só.excluding { campo1, campo2 }— exclui campos específicos.excluding bound elements— remove automaticamente os campos que foram só de entrada do bind, evitando duplicação. Padrão recomendado ao usarinclude ... .*.
Juntando tudo, a view de relatório consome o aspect ABC assim:
@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'Sales Report by Customer'
define view entity Z_SALES_REPORT_BY_CUSTOMER
as select from Z_SALES_AGGREGATED as S
bind aspect Z_CUSTOMER_ABC_CLASSIFICATION (
total_revenue => $projection.YearlyRevenue
)
{
key S.customer_id,
S.customer_name,
S.YearlyRevenue,
S.country,
include Z_CUSTOMER_ABC_CLASSIFICATION.*
excluding bound elements
}Uma segunda view, num contexto totalmente diferente (pipeline comercial), reusa o mesmo aspect — só muda o campo que ela liga em total_revenue:
bind aspect Z_CUSTOMER_ABC_CLASSIFICATION (
total_revenue => $projection.HistoricalRevenue
)Resultado: a regra de classificação vive em um único lugar. Se o threshold mudar de 1M para 1,5M, você altera no aspect e ambas as views refletem na hora. Não é exatamente isso que a gente quer de um modelo de dados sustentável?
Como passar variáveis de sessão no bind?
O valor ligado a um campo de entrada não precisa vir de uma coluna — pode ser uma variável de sessão, como $session.system_date. Isso é perfeito para cálculos relativos à data atual, como idade. Veja um aspect que calcula idade e faixa etária a partir da data de nascimento:
@EndUserText.label: 'Age and Age Bracket Calculation'
define aspect Z_AGE_CALCULATION
{
birth_date : abap.datn;
reference_dt : abap.datn;
@EndUserText.label: 'Age in Years'
division( days_between( birth_date, reference_dt ), 365, 0 ) as AgeInYears,
@EndUserText.label: 'Age Bracket'
case
when division( days_between( birth_date, reference_dt ), 365, 0 ) < 25 then 'Under 25'
when division( days_between( birth_date, reference_dt ), 365, 0 ) < 35 then '25-34'
when division( days_between( birth_date, reference_dt ), 365, 0 ) < 50 then '35-49'
when division( days_between( birth_date, reference_dt ), 365, 0 ) < 65 then '50-64'
else '65+'
end as AgeBracket
}Na view de funcionários, a data de referência é ligada à data do sistema via sessão:
define view entity Z_EMPLOYEE_DEMOGRAPHICS
as select from Z_EMPLOYEE as E
bind aspect Z_AGE_CALCULATION (
birth_date => $projection.BirthDate,
reference_dt => $session.system_date
)
{
key E.employee_id,
E.full_name,
E.department,
E.BirthDate,
include Z_AGE_CALCULATION.*
excluding bound elements
}Como padronizar anotações analíticas com aspects?
Um dos usos mais valiosos do aspect nem é cálculo — é garantir que toda view de KPI exponha as medidas com as mesmas anotações analíticas, necessárias para SAC e Analytics for RAP. Em vez de repetir cinco linhas de anotação em cada view, você as concentra no aspect:
define aspect Z_KPI_MEASURE_ANNOTATIONS
{
raw_amount : abap.curr( 15, 2 );
raw_currency : abap.cuky;
@EndUserText.label: 'KPI Amount'
@Semantics.amount.currencyCode: 'KpiCurrency'
@AnalyticsDetails.measure.dataPoint.criticalityCalculation.improvementDirection: #INCREASE
raw_amount as KpiAmount,
raw_currency as KpiCurrency
}Cada view de KPI (revenue, EBITDA, margem) faz o bind e ganha automaticamente o pacote completo de anotações — sem omissões e sem divergência entre relatórios.
Como incluir só alguns campos ou excluir outros?
Nem toda view quer todos os campos do aspect. O include aceita seleção pontual, e o excluding remove campos específicos do .*:
// Só um campo do aspect
{
key T.ticket_id,
include Z_SLA_COMPLIANCE.SlaStatus
}
// Todos, menos um
{
key T.ticket_id,
include Z_SLA_COMPLIANCE.*
excluding { ResolutionMinutes }
excluding bound elements
}Quais são as limitações de um CDS Aspect?
O aspect é poderoso para reuso, mas tem fronteiras claras. Conhecê-las evita tentar usá-lo onde ele não cabe.
Restrição | Detalhe |
|---|---|
Sem | Não é view; não aparece em |
Sem OData / ABAP SQL diretos | Consuma via uma view que inclui o aspect. |
Bind manual obrigatório | Você mapeia explicitamente cada campo de entrada. |
Sem composições/aggregates | Não substitui modelagem de aggregate root. |
Sem joins próprios | Não tem |
Só em view entities | Views DDIC clássicas ( |
Quais são as boas práticas e armadilhas?
O aspect entrega integridade por ser single source of truth: uma correção propaga para todas as views, e uma omissão se torna impossível, já que a referência é direta. Para extrair valor disso de forma sustentável, vale um checklist mental:
Extraia só quando a lógica aparece em 3+ views — reuso real, não abstração prematura.
Mantenha o aspect coeso: uma responsabilidade clara por aspect.
Documente o aspect e cada campo com
@EndUserText.label.Dê nomes que descrevem a regra (
Z_CUSTOMER_ABC_CLASSIFICATION), nuncaZ_ASPECT_001.Use
excluding bound elementscominclude ... .*para não duplicar os campos de entrada.Coloque as anotações semânticas/analíticas no aspect, não nas views que o consomem.
Perguntas frequentes
Posso consultar um CDS Aspect com SELECT?
Não. O aspect não é uma data source e não tem representação SQL no banco — SELECT FROM aspect não compila. Ele só é consumido dentro de uma view entity, via bind aspect e include. Se você precisa de algo consultável, crie uma view e inclua o aspect nela.
Qual a diferença entre CDS Aspect e CDS Scalar Function?
O aspect encapsula expressões SQL declarativas (campos, CASE, funções built-in) reutilizadas entre views. A Scalar Function devolve um valor único e pode ser chamada em SELECT, WHERE e ABAP. Para lógica procedural ou um valor consumível em qualquer lugar, use a função; para um bloco de elementos/anotações reusado em views, use o aspect.
Aspect funciona em views DDIC clássicas (DEFINE VIEW)?
Não. CDS Aspects são suportados apenas em view entities modernas (e projection views transacionais). A sintaxe antiga DEFINE VIEW baseada em DDIC não os reconhece. É mais um motivo para migrar suas views para view entities nas releases atuais.
O que faz o excluding bound elements?
Ao usar include aspect.*, os campos que você apenas mapeou no bind (as entradas) viriam duplicados na lista de elementos. O excluding bound elements remove esses campos de entrada automaticamente, deixando só os calculados. É o padrão recomendado para evitar duplicação no SELECT list.
Aspect e o "aspect" de access control são a mesma coisa?
Não — apenas compartilham o nome. O aspect deste post é de reuso de campos e anotações. O outro, usado em DCL com DEFINE ACCESSPOLICY e pfcg_auth, serve para controle de acesso por linha. São artefatos distintos com propósitos diferentes; cuidado para não misturá-los em pesquisas e documentação.
Conclusão
CDS Aspects trazem ao ABAP CDS o que faltava para modelos grandes: reuso real de cálculos e anotações, com uma fonte única de verdade. Defina a regra uma vez com DEFINE ASPECT, ligue as entradas com bind aspect e traga os campos com include — e nunca mais persiga uma fórmula divergente espalhada por dez views.
A regra de bolso continua valendo: extraia um aspect quando a duplicação for real (3+ views), mantenha-o coeso e nomeie pela regra de negócio. Reserve a indireção para onde ela paga — e deixe joins, persistência e lógica procedural para os artefatos certos.
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.