Перейти до вмісту

BDL — Behavior Definition Language

BDL (Behavior Definition Language) — декларативна мова, якою описується transactional behavior RAP business object. Файл BDEF (behavior definition) каже фреймворку: «цей BO — managed, є такі-то поля, підтримує create/update/delete, має ось цю action, блокування через master, авторизація — global». А вже ABAP behavior pool (ABP) реалізує саму логіку handler-методів там, де BDL каже, що вона потрібна.

Головна ідея: максимум у BDL — мінімум у ABP. Чим більше можна декларувати (замість писати імперативний код), тим стабільніше BO і тим менше помилок.

managed implementation in class zbp_r_order unique;
strict ( 2 );
with draft;
define behavior for ZR_Order alias Order
persistent table zorder
draft table zorder_d
etag master LocalLastChangedAt
lock master
total etag LastChangedAt
authorization master ( instance )
{
field ( readonly ) OrderId, CreatedBy, CreatedAt;
field ( mandatory ) Customer, Total;
create;
update;
delete;
association _Items { create; }
action ( features : instance ) approve result [1] $self;
action cancel;
validation check_total on save { create; update; field Total; }
determination calc_totals on modify { create; field Items; }
draft action Edit;
draft action Activate optimized;
draft action Discard;
draft action Resume;
draft determine action Prepare { validation check_total; }
mapping for zorder corresponding { OrderId = order_id; Customer = customer; }
}
define behavior for ZR_OrderItem alias OrderItem
persistent table zorderitem
lock dependent by _Order
authorization dependent by _Order
{
field ( readonly ) OrderId, Position;
update;
delete;
}

Далі розберемо компоненти.

Header — властивості всього BO

Section titled “Header — властивості всього BO”
managed implementation in class zbp_r_order unique; " managed
unmanaged implementation in class zbp_r_order_u unique; " unmanaged
managed with unmanaged save ... " гібрид
managed with additional save ... " managed + розширена save-фаза
  • managed — фреймворк тримає транзакційний буфер і робить CRUD автоматично. Для грінфілду.
  • unmanaged — buffer і всі операції пишемо самі. Для браунфілду, інтеграції з legacy.
  • with unmanaged save — managed interaction, але save-фаза ручна (напр., треба писати у стару таблицю через FM).
  • with additional save — стандартний save + додатковий метод save_modified для change docs, подій, логів.
strict ( 2 );

Вмикає суворі syntax-перевірки BDEF. Завжди — версія 2 (найновіша). Без strict компілятор прощає багато дрібниць, які потім стріляють у runtime.

with draft;
with collaborative draft;

with draft вмикає draft-сценарій (чернетки на UI, автозбереження). Потрібна окрема draft table з адмін-ключами. collaborative draft — кілька користувачів працюють над тим самим draft одночасно.

auxiliary class zcl_order_helpers;

Допоміжні класи з додатковими правами доступу до BO (можуть робити IN LOCAL MODE).

define behavior for ZR_Order alias Order
external 'Order' " імʼя в OData metadata
implementation in class zbp_r_order unique

alias — коротка назва, яку видно у handler-методах і derived types. external — публічне імʼя у OData.

persistent table zorder
draft table zorder_d

persistent — де зберігаються активні дані. draft — де draft-instances. Ім’я полів у draft table повинно співпадати з entity; плюс обовʼязкова адмін-структура:

"%admin" : include sych_bdl_draft_admin_inc;
" Optimistic (через ETag)
etag master LocalLastChangedAt
etag dependent by _Order
total etag LastChangedAt " тільки для draft-enabled BO
" Pessimistic (lock table)
lock master
lock master unmanaged " ти реалізовуєш lock сам
lock dependent by _Order
  • etag master — поле з timestamp, що оновлюється при кожному write. OData-клієнт повинен передати ETag — якщо не збігається, write відхиляється.
  • total etag — для draft. Порівнюється між активним instance та draft при resume.
  • lock master — ексклюзивний lock на запис у sm12-стилі.
  • lock dependent by _Assoc — child блокується через parent (кращий варіант для composition).
early numbering
late numbering
field ( numbering : managed ) OrderId

Керує, хто і коли заповнює ключ:

ВаріантКолиХтоBDL
External earlyinteraction phaseспоживач (UI)дефолт
Managed internal earlyinteraction phaseфреймворк (UUID)field ( numbering : managed )
Unmanaged internal earlyinteraction phaseABP (FOR NUMBERING)early numbering
Late numberingsave sequencesaver method adjust_numberslate numbering
authorization master ( global )
authorization master ( instance )
authorization master ( global, instance )
authorization master ( none )
authorization dependent by _Order
  • global — перевірка ролі (без конкретного instance, перед create).
  • instance — перевірка залежно від стану instance (хто може update цей конкретний рядок).
  • none — нічого не перевіряти (обережно).
  • dependent by _Assoc — child наслідує дозволи parent через composition.

Для global/instance в ABP мають бути методи FOR GLOBAL AUTHORIZATION / FOR INSTANCE AUTHORIZATION.

Entity body — всередині фігурних дужок

Section titled “Entity body — всередині фігурних дужок”
field ( readonly ) OrderId, CreatedBy;
field ( readonly : create ) Status;
field ( readonly : update ) Customer;
field ( mandatory ) Customer;
field ( mandatory : create ) OrderId;
field ( suppress ) InternalField; " прибрати з derived types
field ( features : instance ) DynamicField; " динамічна видимість

Поле одночасно може бути mandatory : create і readonly : update — типова комбінація для ключа в external numbering.

create;
update;
delete;
" з доповненнями
internal create; " внутрішнє — не для OData
create ( authorization : none );
update ( features : instance );
delete ( authorization : update ); " delete використовує auth від update
create { default function GetDefaults; } " UI-дефолти

Read завжди неявно дозволено — окремого ключового слова немає.

Actions — нестандартні операції

Section titled “Actions — нестандартні операції”
action approve; " регулярна action
internal action recalc; " тільки з BO
static action cleanup; " не привʼязана до instance
repeatable action log; " можна викликати багато разів
action act parameter ZR_ActParam; " з input
action act result [1] $self; " з output (selfreturning)
action act result [0..*] selective SomeEntity; " масив + selective
factory action copy [1]; " створення instance
default factory action newFromTemplate [1]; " default для UI
save( finalize ) action rebuild_index; " save-time action
action ( features : global ) act1;
action ( features : instance ) act2;
action ( precheck ) act3;
action ( authorization : update ) act4;
action ( lock : none ) act5;

Cardinality у [...]: [1], [0..1], [0..*], [1..*].

Functions — read-only нестандартні

Section titled “Functions — read-only нестандартні”
function calcTotal result [1] $self;
static function getDefaults result [1] ZR_DefaultsType;

Function не змінює стан, не блокує. Використовується для обчислень, пошуків, lookup.

association _Items; " read-by-assoc автоматично
association _Items { create; } " + create-by-assoc
association _Items {
create;
link action attachItem;
unlink action detachItem;
inverse function findParentByItem;
}
validation check_total on save { create; update; field Total; }
validation check_status on save { delete; }
determination fill_defaults on modify { create; }
determination recalc_total on modify { field Quantity, Price; }
determination snapshot on save { create; }
  • Validation — перевіряє дані, може відхилити save. Реалізується у FOR VALIDATE.
  • Determination — змінює дані при тригері. on modify — одразу після зміни у буфері; on save — під час save sequence. Реалізується у FOR DETERMINE.
determine action revalidate {
determination recalc_total;
determination ( always ) snapshot; " завжди, навіть без trigger
validation check_total;
}

Дозволяє вручну викликати набір determinations/validations (напр., з UI «Перевірити»).

draft action Edit;
draft action Activate optimized; " optimized — дешевший save для draft
draft action Discard;
draft action Resume;
draft determine action Prepare { validation check_total; }

Усі чотири (Edit, Activate, Discard, Resume) + Prepare обовʼязкові для draft-enabled BO. Імплементація — фреймворком; за бажанням розширюєш через with additional implementation.

side effects {
field Quantity affects field Total; " зміна Quantity → reload Total на UI
$self affects field StatusText;
action approve affects field *, messages; " reload усього + messages
determine action revalidate
executed on field Quantity, field Price
affects field Total;
event priceChanged affects field Price;
field StatusCode affects permissions( action cancel ); " reload feature control
}

Side effects — про UX на UI, не про бізнес-логіку. Вони дають Fiori команду «перечитати це поле», коли щось змінилось. Без implementation у ABP.

event orderCreated;
event orderApproved parameter ZR_OrderEventParam;
event orderClosed deep parameter ZR_OrderDeepEventParam;
managed event orderSummary on orderCreated parameter ZR_Summary;
event priceChanged for side effects;

Піднімаються через RAISE ENTITY EVENT у saver method (save_modified). Потребують event binding, щоб мапитись на конкретний channel (SAP Event Mesh, logging, тощо).

mapping for zorder corresponding
{
OrderId = order_id;
Customer = customer_name;
Total = total_amount;
}

Потрібне коли поля CDS і DB-таблиці називаються по-різному. Для managed BO без mapping — warning; може зламати auto-save.

Коли над base BDEF робиш projection (обмежений інтерфейс для UI/API):

projection;
strict ( 2 );
use draft;
define behavior for ZC_Order alias Order
use etag
{
use create;
use update;
use delete;
use action approve;
use action cancel;
use association _Items { create; }
}

Projection не визначає поведінку заново — тільки вказує, які операції base BDEF включено у цей шар.

Адаптовано з 36_RAP_Behavior_Definition_Language.md (Apache 2.0). Детальніше — в оригіналі.