Domain-Driven Design con RAP: Modelar dominios de negocio

Kategorie
RAP
Veröffentlicht
Autor
Johannes

Domain-Driven Design (DDD) es un enfoque de desarrollo de software que coloca el dominio de negocio en el centro. RAP (RESTful ABAP Programming) ofrece condiciones ideales para implementar principios DDD: Business Objects, Entities y Compositions reflejan directamente los conceptos DDD.

Por que DDD con RAP?

Concepto DDDImplementacion RAPVentaja
Bounded ContextSoftware Component / PackageClara delimitacion de areas funcionales
AggregateBusiness Object (Root Entity)Limites de consistencia definidos
EntityCDS View EntityIdentidad y ciclo de vida
Value ObjectCDS Structure / Embedded ViewObjetos de valor inmutables
Domain EventRAP Business EventAcoplamiento debil entre contextos
RepositoryRAP Runtime (EML)Abstraccion de la persistencia

Conceptos basicos de DDD

Bounded Context

Un Bounded Context es un limite logico dentro del cual aplica un modelo de dominio especifico. En ABAP Cloud, esto corresponde tipicamente a un Software Component o un arbol de paquetes.

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

Aggregate y Root Entity

Un Aggregate es un grupo de objetos relacionados con una Root Entity. En RAP, la Root Entity corresponde al Business Object con 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,
" Campos administrativos
@Semantics.user.createdBy: true
created_by as CreatedBy,
@Semantics.systemDateTime.createdAt: true
created_at as CreatedAt,
" Asociaciones
_Items,
_Customer
}

Child Entity (Parte del Aggregate)

Las Entities dentro de un Aggregate solo se alcanzan a traves de la Root Entity:

" CHILD ENTITY - Parte del Aggregate Order
@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,
" Campo calculado
quantity * unit_price as LineTotal,
_Order,
_Product
}

Value Object

Los Value Objects no tienen identidad propia y se definen por sus atributos. En RAP se modelan como estructuras embebidas o campos calculados:

" Value Object como 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;
}
" Uso en Entity
define root view entity ZI_Customer
as select from zcustomer
{
key customer_id as CustomerId,
customer_name as CustomerName,
" Value Object: Direccion
street as AddressStreet,
house_number as AddressHouseNumber,
postal_code as AddressPostalCode,
city as AddressCity,
country as AddressCountry
}

Implementar reglas de Aggregate en RAP

Regla 1: Acceso solo a traves del Aggregate Root

La Behavior Definition asegura que las Child Entities solo se manipulen a traves de la 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 se crean/modifican a traves de Order
association _Items { create; }
" Asegurar invariantes del Aggregate
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;
" Sin creacion directa - solo a traves de Order
field ( readonly ) OrderId;
association _Order;
}

Regla 2: Proteger invariantes

Las invariantes del Aggregate se verifican en 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.
" Verificar invariantes del Aggregate
READ ENTITIES OF zi_order IN LOCAL MODE
ENTITY Order
ALL FIELDS WITH CORRESPONDING #( keys )
RESULT DATA(lt_orders).
" Cargar 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).
" Invariante 1: Se requiere al menos un Item
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 = 'El pedido debe tener al menos una posicion'
)
) TO reported-order.
APPEND VALUE #( %tky = ls_order-%tky ) TO failed-order.
CONTINUE.
ENDIF.
" Invariante 2: Total debe coincidir con 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 = 'El total no coincide con las posiciones'
)
) TO reported-order.
APPEND VALUE #( %tky = ls_order-%tky ) TO failed-order.
ENDIF.
ENDLOOP.
ENDMETHOD.
ENDCLASS.

Regla 3: Consistencia transaccional

Un Aggregate siempre se guarda como un todo:

METHOD create_order_with_items.
" Deep Insert - Crear Aggregate como un todo
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).
" Todo o nada - transaccional
IF lt_failed IS NOT INITIAL.
" Rollback automatico por RAP Runtime
RAISE EXCEPTION TYPE cx_order_creation_failed.
ENDIF.
COMMIT ENTITIES.
ENDMETHOD.

Implementar Domain Services

Los Domain Services encapsulan logica de negocio que no pertenece a una sola Entity:

" Interfaz para 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.
" Implementacion
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.
" Calcular subtotal
DATA(lv_subtotal) = REDUCE decfloat34(
INIT sum = CONV decfloat34( 0 )
FOR item IN it_items
NEXT sum = sum + ( item-quantity * item-unit_price )
).
" Aplicar descuento por cliente
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.
" Validar y aplicar codigo de descuento
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.

Usar Domain Service en 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.
" Cargar Order e 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).
" Instanciar Domain Service
DATA(lo_pricing) = NEW zcl_pricing_service( ).
LOOP AT lt_orders INTO DATA(ls_order).
" Obtener Items y datos de cliente
DATA(lt_order_items) = FILTER #( lt_items
WHERE OrderId = ls_order-OrderId ).
DATA(ls_customer) = VALUE #( lt_customers[
CustomerId = ls_order-CustomerId ] OPTIONAL ).
" Llamar 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 ).
" Actualizar 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.
" Retornar resultado
READ ENTITIES OF zi_order IN LOCAL MODE
ENTITY Order
ALL FIELDS WITH CORRESPONDING #( keys )
RESULT result.
ENDMETHOD.
ENDCLASS.

Domain Events en RAP

Los Domain Events senalan eventos de negocio importantes y permiten acoplamiento debil entre Bounded Contexts:

Definir Events

" Behavior Definition con Events
managed implementation in class zbp_i_order unique;
define behavior for ZI_Order alias Order
{
" ... Operaciones estandar ...
" Declarar 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 Parameters como estructura

" Definicion de Event Payload
@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;
}

Disparar Events

CLASS lhc_order IMPLEMENTATION.
METHOD confirmOrder.
" Cambiar 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.
" Leer datos actuales
READ ENTITIES OF zi_order IN LOCAL MODE
ENTITY Order
ALL FIELDS WITH CORRESPONDING #( keys )
RESULT DATA(lt_orders).
" Disparar 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.
" Resultado
result = CORRESPONDING #( lt_orders ).
ENDMETHOD.
ENDCLASS.

Event Handler en otro 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'.
" Leer datos del Event
DATA ls_event TYPE zs_order_confirmed_event.
ls_event = CORRESPONDING #( is_event_data ).
" Crear Shipment en 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

Un concepto central de DDD es el Ubiquitous Language - un vocabulario comun para el area funcional y desarrollo:

CDS Naming Conventions

" Nombres de Entity corresponden a terminos de negocio
define root view entity ZI_SalesOrder " Pedido de cliente
define view entity ZI_SalesOrderItem " Posicion de pedido
define view entity ZI_DeliveryNote " Nota de entrega
define view entity ZI_Invoice " Factura
define root view entity ZI_Customer " Cliente
" Nombres de campos son autoexplicativos
@EndUserText.label: 'Pedido de cliente'
define root view entity ZI_SalesOrder
{
key order_number as OrderNumber, " Numero de pedido
customer_number as CustomerNumber, " Numero de cliente
order_date as OrderDate, " Fecha de pedido
requested_delivery as RequestedDelivery, " Fecha de entrega deseada
net_value as NetValue, " Valor neto
tax_amount as TaxAmount, " Importe de impuestos
gross_value as GrossValue, " Valor bruto
payment_terms as PaymentTerms, " Condiciones de pago
shipping_method as ShippingMethod, " Metodo de envio
order_status as OrderStatus " Estado del pedido
}

Tipos de datos especificos del dominio

" Elementos de datos propios para terminos de negocio
@EndUserText.label: 'Numero de pedido'
@AbapCatalog.dataMaintenance: #ALLOWED
define type zdd_order_number : abap.numc(10);
@EndUserText.label: 'Numero de cliente'
define type zdd_customer_number : abap.numc(10);
@EndUserText.label: 'Estado del pedido'
define type zdd_order_status : abap.char(2);
" Uso en tabla y 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;
}

Integracion de Bounded Context

Anti-Corruption Layer

Un Anti-Corruption Layer protege el propio Bounded Context de modelos de datos externos:

" Interfaz al sistema externo
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.
" Obtener producto externo
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.
" Traducir a modelo de dominio propio
rs_product = translate_product( ls_external ).
ENDMETHOD.
METHOD translate_product.
" Mapeo a estructura propia
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.
" Mapear categorias externas a internas
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

Un Context Map documenta las relaciones entre 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) │ │
│ └──────────────┘ │
└─────────────────────────────────────────────────────────────────┘

Mejores practicas para DDD con RAP

1. Establecer limites de Aggregate correctamente

" BIEN: Order es Aggregate Root con Items como Children
define root view entity ZI_Order
composition [0..*] of ZI_OrderItem as _Items " Parte del Aggregate
" MAL: Aggregate demasiado grande
define root view entity ZI_Order
composition [0..*] of ZI_OrderItem as _Items
composition [0..*] of ZI_Invoice as _Invoices " Aggregate propio!
composition [0..*] of ZI_Shipment as _Shipments " Aggregate propio!

2. Consistencia dentro del Aggregate

" Determination actualiza valores derivados en el Aggregate
determination calculateTotals on modify {
field Quantity, UnitPrice; " Al cambiar Items
}
METHOD calculateTotals.
" Sumar todos los Items
DATA(lv_total) = REDUCE decfloat34(
INIT sum = CONV decfloat34( 0 )
FOR item IN lt_items
NEXT sum = sum + item-LineTotal
).
" Actualizar 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. Abstraer Repositories

" Interfaz de Repository para 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.
" Implementacion basada en RAP
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.
" Cargar Items
READ ENTITIES OF zi_order
ENTITY Order BY \_Items
ALL FIELDS WITH VALUE #( ( OrderId = iv_order_id ) )
RESULT DATA(lt_items).
" Componer Aggregate
rs_order-header = CORRESPONDING #( lt_orders[ 1 ] ).
rs_order-items = CORRESPONDING #( lt_items ).
ENDMETHOD.
ENDCLASS.

4. Metodos de negocio en lugar de tecnicos

" BIEN: Action de negocio
action confirmOrder result [1] $self;
action shipOrder parameter ZA_ShipmentDetails result [1] $self;
action cancelOrder parameter ZA_CancellationReason result [1] $self;
" MAL: Action tecnica
action setStatusConfirmed result [1] $self;
action updateShippingData parameter ZA_ShippingData result [1] $self;

Temas relacionados

Conclusion

Domain-Driven Design y RAP se complementan excelentemente:

  • Aggregate = Business Object: La arquitectura RAP con Root y Child Entities corresponde directamente al concepto de Aggregate
  • Bounded Contexts = Software Components: Las estructuras de paquetes definen limites claros
  • Domain Events = RAP Business Events: Acoplamiento debil entre contextos
  • Repository = EML/RAP Runtime: La persistencia es abstraida por el framework

DDD requiere mas analisis inicial, pero se compensa con mejor mantenibilidad, comunicacion mas clara entre negocio y desarrollo, y arquitectura mas flexible. RAP proporciona la base tecnica para implementar estos conceptos elegantemente.