Domain-Driven Design with RAP: Modeling Business Domains

Category
RAP
Published
Author
Johannes

Domain-Driven Design (DDD) is an approach to software development that puts the business domain at the center. RAP (RESTful ABAP Programming) provides ideal conditions for implementing DDD principles: Business Objects, Entities, and Compositions directly mirror DDD concepts.

Why DDD with RAP?

DDD ConceptRAP ImplementationAdvantage
Bounded ContextSoftware Component / PackageClear separation of business areas
AggregateBusiness Object (Root Entity)Consistency boundaries defined
EntityCDS View EntityIdentity and lifecycle
Value ObjectCDS Structure / Embedded ViewImmutable value objects
Domain EventRAP Business EventLoose coupling between contexts
RepositoryRAP Runtime (EML)Abstraction of persistence

DDD Core Concepts

Bounded Context

A Bounded Context is a logical boundary within which a specific domain model applies. In ABAP Cloud, this typically corresponds to a Software Component or a package tree.

┌─────────────────────────────────────────────────────────────┐
│ ENTERPRISE │
│ ┌─────────────────────┐ ┌─────────────────────────────┐ │
│ │ SALES CONTEXT │ │ LOGISTICS CONTEXT │ │
│ │ ┌───────────────┐ │ │ ┌───────────────────────┐ │ │
│ │ │ Order (BO) │ │ │ │ Shipment (BO) │ │ │
│ │ │ Customer │──┼────┼──│ DeliveryAddress │ │ │
│ │ │ OrderItem │ │ │ │ ShipmentItem │ │ │
│ │ └───────────────┘ │ │ └───────────────────────┘ │ │
│ │ ┌───────────────┐ │ │ ┌───────────────────────┐ │ │
│ │ │ Product (ref) │◄─┼────┼──│ Inventory (BO) │ │ │
│ │ └───────────────┘ │ │ │ StockLevel │ │ │
│ └─────────────────────┘ │ └───────────────────────┘ │ │
│ └─────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘

Aggregate and Root Entity

An Aggregate is a group of related objects with a Root Entity. In RAP, the Root Entity corresponds to the Business Object with define root view entity:

" ROOT ENTITY = Aggregate Root
@AbapCatalog.viewEnhancementCategory: [#NONE]
@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'Order - Root Entity (Aggregate)'
define root view entity ZI_Order
as select from zorder
composition [0..*] of ZI_OrderItem as _Items
association [0..1] to ZI_Customer as _Customer
on $projection.CustomerId = _Customer.CustomerId
{
key order_id as OrderId,
customer_id as CustomerId,
order_date as OrderDate,
@Semantics.amount.currencyCode: 'CurrencyCode'
total_amount as TotalAmount,
@Semantics.currencyCode: true
currency_code as CurrencyCode,
status as Status,
" Administrative fields
@Semantics.user.createdBy: true
created_by as CreatedBy,
@Semantics.systemDateTime.createdAt: true
created_at as CreatedAt,
" Associations
_Items,
_Customer
}

Child Entity (Part of the Aggregate)

Entities within an Aggregate are only reached through the Root Entity:

" CHILD ENTITY - Part of the Order Aggregate
@AbapCatalog.viewEnhancementCategory: [#NONE]
@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'Order Item - Child Entity'
define view entity ZI_OrderItem
as select from zorder_item
association to parent ZI_Order as _Order
on $projection.OrderId = _Order.OrderId
association [1..1] to ZI_Product as _Product
on $projection.ProductId = _Product.ProductId
{
key order_id as OrderId,
key item_id as ItemId,
product_id as ProductId,
quantity as Quantity,
@Semantics.amount.currencyCode: 'CurrencyCode'
unit_price as UnitPrice,
@Semantics.currencyCode: true
currency_code as CurrencyCode,
" Calculated field
quantity * unit_price as LineTotal,
_Order,
_Product
}

Value Object

Value Objects have no identity of their own and are defined by their attributes. In RAP, they are modeled as embedded structures or calculated fields:

" Value Object as CDS Structure
@EndUserText.label: 'Address Value Object'
define structure zs_address {
street : abap.char(60);
house_number: abap.char(10);
postal_code : abap.char(10);
city : abap.char(40);
country : land1;
}
" Usage in Entity
define root view entity ZI_Customer
as select from zcustomer
{
key customer_id as CustomerId,
customer_name as CustomerName,
" Value Object: Address
street as AddressStreet,
house_number as AddressHouseNumber,
postal_code as AddressPostalCode,
city as AddressCity,
country as AddressCountry
}

Implementing Aggregate Rules in RAP

Rule 1: Access Only Through Aggregate Root

The Behavior Definition enforces that Child Entities are only manipulated through the Root Entity:

managed implementation in class zbp_i_order unique;
strict ( 2 );
define behavior for ZI_Order alias Order
persistent table zorder
lock master
authorization master ( instance )
etag master LastChangedAt
{
create;
update;
delete;
" Items are created/changed through Order
association _Items { create; }
" Ensure aggregate invariants
validation validateOrderConsistency on save { create; update; }
" Domain Events
event OrderCreated;
event OrderConfirmed;
}
define behavior for ZI_OrderItem alias OrderItem
persistent table zorder_item
lock dependent by _Order
authorization dependent by _Order
{
update;
delete;
" No direct creation - only through Order
field ( readonly ) OrderId;
association _Order;
}

Rule 2: Protect Invariants

Aggregate invariants are checked in Validations:

CLASS lhc_order DEFINITION INHERITING FROM cl_abap_behavior_handler.
PRIVATE SECTION.
METHODS validateOrderConsistency FOR VALIDATE ON SAVE
IMPORTING keys FOR Order~validateOrderConsistency.
ENDCLASS.
CLASS lhc_order IMPLEMENTATION.
METHOD validateOrderConsistency.
" Check aggregate invariants
READ ENTITIES OF zi_order IN LOCAL MODE
ENTITY Order
ALL FIELDS WITH CORRESPONDING #( keys )
RESULT DATA(lt_orders).
" Load items
READ ENTITIES OF zi_order IN LOCAL MODE
ENTITY Order BY \_Items
ALL FIELDS WITH CORRESPONDING #( keys )
RESULT DATA(lt_items).
LOOP AT lt_orders INTO DATA(ls_order).
" Invariant 1: At least one item required
DATA(lt_order_items) = FILTER #( lt_items
WHERE OrderId = ls_order-OrderId ).
IF lines( lt_order_items ) = 0.
APPEND VALUE #(
%tky = ls_order-%tky
%msg = new_message_with_text(
severity = if_abap_behv_message=>severity-error
text = 'Order must contain at least one item'
)
) TO reported-order.
APPEND VALUE #( %tky = ls_order-%tky ) TO failed-order.
CONTINUE.
ENDIF.
" Invariant 2: Total amount must match items
DATA(lv_calculated_total) = REDUCE decfloat34(
INIT sum = CONV decfloat34( 0 )
FOR item IN lt_order_items
NEXT sum = sum + ( item-Quantity * item-UnitPrice )
).
IF ls_order-TotalAmount <> lv_calculated_total.
APPEND VALUE #(
%tky = ls_order-%tky
%element-TotalAmount = if_abap_behv=>mk-on
%msg = new_message_with_text(
severity = if_abap_behv_message=>severity-error
text = 'Total amount does not match items'
)
) TO reported-order.
APPEND VALUE #( %tky = ls_order-%tky ) TO failed-order.
ENDIF.
ENDLOOP.
ENDMETHOD.
ENDCLASS.

Rule 3: Transactional Consistency

An Aggregate is always saved as a whole:

METHOD create_order_with_items.
" Deep Insert - Create aggregate as a whole
MODIFY ENTITIES OF zi_order
ENTITY Order
CREATE FIELDS ( CustomerId OrderDate TotalAmount CurrencyCode Status )
WITH VALUE #( (
%cid = 'ORDER_1'
CustomerId = iv_customer_id
OrderDate = cl_abap_context_info=>get_system_date( )
TotalAmount = lv_total
CurrencyCode = 'EUR'
Status = 'NEW'
) )
CREATE BY \_Items
FIELDS ( ProductId Quantity UnitPrice CurrencyCode )
WITH VALUE #(
( %cid_ref = 'ORDER_1'
%target = VALUE #(
( %cid = 'ITEM_1'
ProductId = 'PROD001'
Quantity = 2
UnitPrice = '100.00'
CurrencyCode = 'EUR' )
( %cid = 'ITEM_2'
ProductId = 'PROD002'
Quantity = 1
UnitPrice = '250.00'
CurrencyCode = 'EUR' )
)
)
)
MAPPED DATA(lt_mapped)
FAILED DATA(lt_failed)
REPORTED DATA(lt_reported).
" All or nothing - transactional
IF lt_failed IS NOT INITIAL.
" Rollback automatic through RAP Runtime
RAISE EXCEPTION TYPE cx_order_creation_failed.
ENDIF.
COMMIT ENTITIES.
ENDMETHOD.

Implementing Domain Services

Domain Services encapsulate business logic that doesn’t belong to a single entity:

" Interface for Domain Service
INTERFACE zif_pricing_service.
METHODS calculate_order_total
IMPORTING
it_items TYPE ztt_order_items
iv_customer_tier TYPE zdd_customer_tier
RETURNING
VALUE(rs_result) TYPE zs_pricing_result.
METHODS apply_discount
IMPORTING
iv_subtotal TYPE decfloat34
iv_discount_code TYPE zdd_discount_code
RETURNING
VALUE(rv_total) TYPE decfloat34.
ENDINTERFACE.
" Implementation
CLASS zcl_pricing_service DEFINITION
PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES zif_pricing_service.
PRIVATE SECTION.
METHODS get_customer_discount
IMPORTING iv_tier TYPE zdd_customer_tier
RETURNING VALUE(rv_discount) TYPE decfloat34.
ENDCLASS.
CLASS zcl_pricing_service IMPLEMENTATION.
METHOD zif_pricing_service~calculate_order_total.
" Calculate subtotal
DATA(lv_subtotal) = REDUCE decfloat34(
INIT sum = CONV decfloat34( 0 )
FOR item IN it_items
NEXT sum = sum + ( item-quantity * item-unit_price )
).
" Apply customer discount
DATA(lv_discount) = get_customer_discount( iv_customer_tier ).
DATA(lv_discount_amount) = lv_subtotal * lv_discount / 100.
rs_result-subtotal = lv_subtotal.
rs_result-discount_percentage = lv_discount.
rs_result-discount_amount = lv_discount_amount.
rs_result-total = lv_subtotal - lv_discount_amount.
ENDMETHOD.
METHOD zif_pricing_service~apply_discount.
" Validate and apply discount code
SELECT SINGLE discount_percentage
FROM zdiscount_codes
WHERE code = @iv_discount_code
AND valid_from <= @cl_abap_context_info=>get_system_date( )
AND valid_to >= @cl_abap_context_info=>get_system_date( )
INTO @DATA(lv_percentage).
IF sy-subrc = 0.
rv_total = iv_subtotal * ( 1 - lv_percentage / 100 ).
ELSE.
rv_total = iv_subtotal.
ENDIF.
ENDMETHOD.
METHOD get_customer_discount.
CASE iv_tier.
WHEN 'GOLD'.
rv_discount = 15.
WHEN 'SILVER'.
rv_discount = 10.
WHEN 'BRONZE'.
rv_discount = 5.
WHEN OTHERS.
rv_discount = 0.
ENDCASE.
ENDMETHOD.
ENDCLASS.

Using Domain Service in RAP Action

CLASS lhc_order DEFINITION INHERITING FROM cl_abap_behavior_handler.
PRIVATE SECTION.
METHODS recalculateTotal FOR MODIFY
IMPORTING keys FOR ACTION Order~recalculateTotal
RESULT result.
ENDCLASS.
CLASS lhc_order IMPLEMENTATION.
METHOD recalculateTotal.
" Load Order and Items
READ ENTITIES OF zi_order IN LOCAL MODE
ENTITY Order
ALL FIELDS WITH CORRESPONDING #( keys )
RESULT DATA(lt_orders).
READ ENTITIES OF zi_order IN LOCAL MODE
ENTITY Order BY \_Items
ALL FIELDS WITH CORRESPONDING #( keys )
RESULT DATA(lt_items).
READ ENTITIES OF zi_order IN LOCAL MODE
ENTITY Order BY \_Customer
FIELDS ( CustomerTier )
WITH CORRESPONDING #( keys )
RESULT DATA(lt_customers).
" Instantiate Domain Service
DATA(lo_pricing) = NEW zcl_pricing_service( ).
LOOP AT lt_orders INTO DATA(ls_order).
" Get items and customer data
DATA(lt_order_items) = FILTER #( lt_items
WHERE OrderId = ls_order-OrderId ).
DATA(ls_customer) = VALUE #( lt_customers[
CustomerId = ls_order-CustomerId ] OPTIONAL ).
" Call Domain Service
DATA(ls_pricing) = lo_pricing->zif_pricing_service~calculate_order_total(
it_items = CORRESPONDING #( lt_order_items )
iv_customer_tier = ls_customer-CustomerTier ).
" Update Aggregate
MODIFY ENTITIES OF zi_order IN LOCAL MODE
ENTITY Order
UPDATE FIELDS ( TotalAmount DiscountAmount )
WITH VALUE #( (
%tky = ls_order-%tky
TotalAmount = ls_pricing-total
DiscountAmount = ls_pricing-discount_amount
) ).
ENDLOOP.
" Return result
READ ENTITIES OF zi_order IN LOCAL MODE
ENTITY Order
ALL FIELDS WITH CORRESPONDING #( keys )
RESULT result.
ENDMETHOD.
ENDCLASS.

Domain Events in RAP

Domain Events signal important business occurrences and enable loose coupling between Bounded Contexts:

Defining Events

" Behavior Definition with Events
managed implementation in class zbp_i_order unique;
define behavior for ZI_Order alias Order
{
" ... Standard operations ...
" Declare Domain Events
event OrderCreated parameter zs_order_created_event;
event OrderConfirmed parameter zs_order_confirmed_event;
event OrderShipped parameter zs_order_shipped_event;
event OrderCancelled;
}

Event Parameter as Structure

" Event Payload Definition
@EndUserText.label: 'Order Created Event'
define abstract entity ZA_OrderCreatedEvent
{
OrderId : abap.numc(10);
CustomerId : abap.numc(10);
OrderDate : abap.dats;
TotalAmount : abap.dec(15,2);
CurrencyCode : abap.cuky;
ItemCount : abap.int4;
}

Raising Events

CLASS lhc_order IMPLEMENTATION.
METHOD confirmOrder.
" Change status
MODIFY ENTITIES OF zi_order IN LOCAL MODE
ENTITY Order
UPDATE FIELDS ( Status ConfirmedAt )
WITH VALUE #( FOR key IN keys
( %tky = key-%tky
Status = 'CONFIRMED'
ConfirmedAt = utclong_current( ) ) )
FAILED failed
REPORTED reported.
" Read current data
READ ENTITIES OF zi_order IN LOCAL MODE
ENTITY Order
ALL FIELDS WITH CORRESPONDING #( keys )
RESULT DATA(lt_orders).
" Raise Domain Event
LOOP AT lt_orders INTO DATA(ls_order).
RAISE ENTITY EVENT zi_order~OrderConfirmed
FROM VALUE #( (
%key = ls_order-%key
%param = VALUE zs_order_confirmed_event(
order_id = ls_order-OrderId
customer_id = ls_order-CustomerId
confirmed_at = ls_order-ConfirmedAt
total_amount = ls_order-TotalAmount
currency_code = ls_order-CurrencyCode
)
) ).
ENDLOOP.
" Result
result = CORRESPONDING #( lt_orders ).
ENDMETHOD.
ENDCLASS.

Event Handler in Another Bounded Context

CLASS zcl_logistics_event_handler DEFINITION
PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES if_rap_event_handler.
ENDCLASS.
CLASS zcl_logistics_event_handler IMPLEMENTATION.
METHOD if_rap_event_handler~handle_event.
CASE iv_event_name.
WHEN 'ORDERCONFIRMED'.
" Read event data
DATA ls_event TYPE zs_order_confirmed_event.
ls_event = CORRESPONDING #( is_event_data ).
" Create Shipment in Logistics Context
MODIFY ENTITIES OF zi_shipment
ENTITY Shipment
CREATE FIELDS ( OrderId CustomerId Status )
WITH VALUE #( (
%cid = 'SHIP_1'
OrderId = ls_event-order_id
CustomerId = ls_event-customer_id
Status = 'PENDING'
) )
MAPPED DATA(lt_mapped)
FAILED DATA(lt_failed)
REPORTED DATA(lt_reported).
IF lt_failed IS INITIAL.
COMMIT ENTITIES.
ENDIF.
ENDCASE.
ENDMETHOD.
ENDCLASS.

Ubiquitous Language

A central DDD concept is the Ubiquitous Language - a shared vocabulary for business and development:

CDS Naming Conventions

" Entity names correspond to business terms
define root view entity ZI_SalesOrder " Sales Order
define view entity ZI_SalesOrderItem " Sales Order Item
define view entity ZI_DeliveryNote " Delivery Note
define view entity ZI_Invoice " Invoice
define root view entity ZI_Customer " Customer
" Field names are self-explanatory
@EndUserText.label: 'Sales Order'
define root view entity ZI_SalesOrder
{
key order_number as OrderNumber, " Order Number
customer_number as CustomerNumber, " Customer Number
order_date as OrderDate, " Order Date
requested_delivery as RequestedDelivery, " Requested Delivery Date
net_value as NetValue, " Net Value
tax_amount as TaxAmount, " Tax Amount
gross_value as GrossValue, " Gross Value
payment_terms as PaymentTerms, " Payment Terms
shipping_method as ShippingMethod, " Shipping Method
order_status as OrderStatus " Order Status
}

Domain-Specific Data Types

" Custom data elements for business terms
@EndUserText.label: 'Order Number'
@AbapCatalog.dataMaintenance: #ALLOWED
define type zdd_order_number : abap.numc(10);
@EndUserText.label: 'Customer Number'
define type zdd_customer_number : abap.numc(10);
@EndUserText.label: 'Order Status'
define type zdd_order_status : abap.char(2);
" Usage in table and CDS
define table zsales_order {
key client : abap.clnt not null;
key order_number : zdd_order_number not null;
customer_number : zdd_customer_number;
order_status : zdd_order_status;
}

Bounded Context Integration

Anti-Corruption Layer

An Anti-Corruption Layer protects your Bounded Context from foreign data models:

" Interface to external system
INTERFACE zif_external_product_service.
METHODS get_product
IMPORTING iv_external_id TYPE string
RETURNING VALUE(rs_product) TYPE zs_external_product.
ENDINTERFACE.
" Anti-Corruption Layer
CLASS zcl_product_acl DEFINITION
PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION.
METHODS constructor
IMPORTING io_external_service TYPE REF TO zif_external_product_service.
METHODS get_product_for_order
IMPORTING iv_product_id TYPE zdd_product_id
RETURNING VALUE(rs_product) TYPE zs_order_product
RAISING cx_product_not_found.
PRIVATE SECTION.
DATA mo_external_service TYPE REF TO zif_external_product_service.
METHODS translate_product
IMPORTING is_external TYPE zs_external_product
RETURNING VALUE(rs_internal) TYPE zs_order_product.
ENDCLASS.
CLASS zcl_product_acl IMPLEMENTATION.
METHOD constructor.
mo_external_service = io_external_service.
ENDMETHOD.
METHOD get_product_for_order.
" Fetch external product
DATA(ls_external) = mo_external_service->get_product(
CONV #( iv_product_id ) ).
IF ls_external IS INITIAL.
RAISE EXCEPTION TYPE cx_product_not_found.
ENDIF.
" Translate to own domain model
rs_product = translate_product( ls_external ).
ENDMETHOD.
METHOD translate_product.
" Map to own structure
rs_internal-product_id = is_external-sku.
rs_internal-name = is_external-title.
rs_internal-price = is_external-list_price.
rs_internal-currency = is_external-currency_iso.
" Map external categories to internal
CASE is_external-category_code.
WHEN 'ELEC'.
rs_internal-product_group = 'ELECTRONICS'.
WHEN 'FURN'.
rs_internal-product_group = 'FURNITURE'.
WHEN OTHERS.
rs_internal-product_group = 'OTHER'.
ENDCASE.
ENDMETHOD.
ENDCLASS.

Context Map

A Context Map documents the relationships between Bounded Contexts:

┌─────────────────────────────────────────────────────────────────┐
│ CONTEXT MAP │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────┐ Events ┌──────────────────┐ │
│ │ SALES │ ──────────────────────► │ LOGISTICS │ │
│ │ Context │ OrderConfirmed │ Context │ │
│ │ │ OrderCancelled │ │ │
│ │ [Upstream] │ │ [Downstream] │ │
│ └──────────────┘ └──────────────────┘ │
│ │ │ │
│ │ API Call │ │
│ ▼ │ │
│ ┌──────────────┐ │ │
│ │ PRODUCT │ ◄────────────────────────────────┘ │
│ │ Context │ ACL (Read-Only) │
│ │ │ │
│ │ [Upstream] │ │
│ └──────────────┘ │
│ │ │
│ │ Conformist │
│ ▼ │
│ ┌──────────────┐ │
│ │ EXTERNAL │ │
│ │ Catalog │ │
│ │ (SAP S/4) │ │
│ └──────────────┘ │
└─────────────────────────────────────────────────────────────────┘

Best Practices for DDD with RAP

1. Set Aggregate Boundaries Correctly

" GOOD: Order is Aggregate Root with Items as Children
define root view entity ZI_Order
composition [0..*] of ZI_OrderItem as _Items " Part of the Aggregate
" BAD: Aggregate too large
define root view entity ZI_Order
composition [0..*] of ZI_OrderItem as _Items
composition [0..*] of ZI_Invoice as _Invoices " Own Aggregate!
composition [0..*] of ZI_Shipment as _Shipments " Own Aggregate!

2. Consistency Within the Aggregate

" Determination updates derived values in the Aggregate
determination calculateTotals on modify {
field Quantity, UnitPrice; " On item changes
}
METHOD calculateTotals.
" Sum all items
DATA(lv_total) = REDUCE decfloat34(
INIT sum = CONV decfloat34( 0 )
FOR item IN lt_items
NEXT sum = sum + item-LineTotal
).
" Update Root Entity
MODIFY ENTITIES OF zi_order IN LOCAL MODE
ENTITY Order
UPDATE FIELDS ( TotalAmount )
WITH VALUE #( ( %tky = ls_order-%tky
TotalAmount = lv_total ) ).
ENDMETHOD.

3. Abstract Repositories

" Repository Interface for Aggregate
INTERFACE zif_order_repository.
METHODS find_by_id
IMPORTING iv_order_id TYPE zdd_order_id
RETURNING VALUE(rs_order) TYPE zs_order_aggregate
RAISING cx_not_found.
METHODS find_by_customer
IMPORTING iv_customer_id TYPE zdd_customer_id
RETURNING VALUE(rt_orders) TYPE ztt_order_aggregates.
METHODS save
IMPORTING is_order TYPE zs_order_aggregate
RAISING cx_save_failed.
ENDINTERFACE.
" RAP-based Implementation
CLASS zcl_order_repository DEFINITION
PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES zif_order_repository.
ENDCLASS.
CLASS zcl_order_repository IMPLEMENTATION.
METHOD zif_order_repository~find_by_id.
READ ENTITIES OF zi_order
ENTITY Order
ALL FIELDS WITH VALUE #( ( OrderId = iv_order_id ) )
RESULT DATA(lt_orders)
FAILED DATA(lt_failed).
IF lt_failed IS NOT INITIAL OR lt_orders IS INITIAL.
RAISE EXCEPTION TYPE cx_not_found.
ENDIF.
" Load items
READ ENTITIES OF zi_order
ENTITY Order BY \_Items
ALL FIELDS WITH VALUE #( ( OrderId = iv_order_id ) )
RESULT DATA(lt_items).
" Assemble Aggregate
rs_order-header = CORRESPONDING #( lt_orders[ 1 ] ).
rs_order-items = CORRESPONDING #( lt_items ).
ENDMETHOD.
ENDCLASS.

4. Business Methods Instead of Technical

" GOOD: Business Action
action confirmOrder result [1] $self;
action shipOrder parameter ZA_ShipmentDetails result [1] $self;
action cancelOrder parameter ZA_CancellationReason result [1] $self;
" BAD: Technical Action
action setStatusConfirmed result [1] $self;
action updateShippingData parameter ZA_ShippingData result [1] $self;
  • RAP Basics - Foundation for Business Object development
  • Design Patterns for RAP - Factory, Strategy, and more
  • RAP Business Events - Event-Driven Architecture
  • Clean ABAP - Code quality and naming conventions

Conclusion

Domain-Driven Design and RAP complement each other excellently:

  • Aggregate = Business Object: The RAP architecture with Root and Child Entities directly corresponds to the Aggregate concept
  • Bounded Contexts = Software Components: Package structures define clear boundaries
  • Domain Events = RAP Business Events: Loose coupling between contexts
  • Repository = EML/RAP Runtime: Persistence is abstracted by the framework

DDD requires more analysis initially but pays off through better maintainability, clearer communication between business and development, and more flexible architecture. RAP provides the technical foundation to elegantly implement these concepts.