Domain-Driven Design (DDD) ist ein Ansatz zur Softwareentwicklung, der die Fachdomaene ins Zentrum stellt. RAP (RESTful ABAP Programming) bietet ideale Voraussetzungen, um DDD-Prinzipien umzusetzen: Business Objects, Entities und Compositions spiegeln direkt die DDD-Konzepte wider.
Warum DDD mit RAP?
| DDD-Konzept | RAP-Umsetzung | Vorteil |
|---|---|---|
| Bounded Context | Software Component / Package | Klare Abgrenzung von Fachbereichen |
| Aggregate | Business Object (Root Entity) | Konsistenzgrenzen definiert |
| Entity | CDS View Entity | Identitaet und Lebenszyklus |
| Value Object | CDS Struktur / Embedded View | Unveraenderliche Wertobjekte |
| Domain Event | RAP Business Event | Lose Kopplung zwischen Kontexten |
| Repository | RAP Runtime (EML) | Abstraktion der Persistenz |
DDD Grundkonzepte
Bounded Context
Ein Bounded Context ist eine logische Grenze, innerhalb derer ein bestimmtes Domänenmodell gilt. In ABAP Cloud entspricht dies typischerweise einer Software Component oder einem Package-Baum.
┌─────────────────────────────────────────────────────────────┐│ ENTERPRISE ││ ┌─────────────────────┐ ┌─────────────────────────────┐ ││ │ SALES CONTEXT │ │ LOGISTICS CONTEXT │ ││ │ ┌───────────────┐ │ │ ┌───────────────────────┐ │ ││ │ │ Order (BO) │ │ │ │ Shipment (BO) │ │ ││ │ │ Customer │──┼────┼──│ DeliveryAddress │ │ ││ │ │ OrderItem │ │ │ │ ShipmentItem │ │ ││ │ └───────────────┘ │ │ └───────────────────────┘ │ ││ │ ┌───────────────┐ │ │ ┌───────────────────────┐ │ ││ │ │ Product (ref) │◄─┼────┼──│ Inventory (BO) │ │ ││ │ └───────────────┘ │ │ │ StockLevel │ │ ││ └─────────────────────┘ │ └───────────────────────┘ │ ││ └─────────────────────────────┘ │└─────────────────────────────────────────────────────────────┘Aggregate und Root Entity
Ein Aggregate ist eine Gruppe von zusammengehoerenden Objekten mit einer Root Entity. In RAP entspricht die Root Entity dem Business Object mit 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 Felder @Semantics.user.createdBy: true created_by as CreatedBy, @Semantics.systemDateTime.createdAt: true created_at as CreatedAt,
" Assoziationen _Items, _Customer}Child Entity (Teil des Aggregates)
Entities innerhalb eines Aggregates werden nur ueber die Root Entity erreicht:
" CHILD ENTITY - Teil des Order Aggregates@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,
" Berechnetes Feld quantity * unit_price as LineTotal,
_Order, _Product}Value Object
Value Objects haben keine eigene Identitaet und werden durch ihre Attribute definiert. In RAP werden sie als eingebettete Strukturen oder berechnete Felder modelliert:
" Value Object als 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;}
" Verwendung in Entitydefine root view entity ZI_Customer as select from zcustomer{ key customer_id as CustomerId, customer_name as CustomerName,
" Value Object: Adresse street as AddressStreet, house_number as AddressHouseNumber, postal_code as AddressPostalCode, city as AddressCity, country as AddressCountry}Aggregate-Regeln in RAP umsetzen
Regel 1: Zugriff nur ueber Aggregate Root
Die Behavior Definition erzwingt, dass Child Entities nur ueber die Root Entity manipuliert werden:
managed implementation in class zbp_i_order unique;strict ( 2 );
define behavior for ZI_Order alias Orderpersistent table zorderlock masterauthorization master ( instance )etag master LastChangedAt{ create; update; delete;
" Items werden ueber Order erstellt/geaendert association _Items { create; }
" Aggregate-Invarianten sicherstellen validation validateOrderConsistency on save { create; update; }
" Domain Events event OrderCreated; event OrderConfirmed;}
define behavior for ZI_OrderItem alias OrderItempersistent table zorder_itemlock dependent by _Orderauthorization dependent by _Order{ update; delete;
" Keine direkte Erstellung - nur ueber Order field ( readonly ) OrderId;
association _Order;}Regel 2: Invarianten schuetzen
Aggregate-Invarianten werden in Validations geprueft:
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. " Aggregate-Invarianten pruefen READ ENTITIES OF zi_order IN LOCAL MODE ENTITY Order ALL FIELDS WITH CORRESPONDING #( keys ) RESULT DATA(lt_orders).
" Items laden 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: Mindestens ein Item erforderlich 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 = 'Bestellung muss mindestens eine Position enthalten' ) ) TO reported-order. APPEND VALUE #( %tky = ls_order-%tky ) TO failed-order. CONTINUE. ENDIF.
" Invariante 2: Gesamtbetrag muss mit Items uebereinstimmen 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 = 'Gesamtbetrag stimmt nicht mit Positionen ueberein' ) ) TO reported-order. APPEND VALUE #( %tky = ls_order-%tky ) TO failed-order. ENDIF. ENDLOOP. ENDMETHOD.ENDCLASS.Regel 3: Transaktionale Konsistenz
Ein Aggregate wird immer als Ganzes gespeichert:
METHOD create_order_with_items. " Deep Insert - Aggregate als Ganzes erstellen 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).
" Alles oder nichts - transaktional IF lt_failed IS NOT INITIAL. " Rollback automatisch durch RAP Runtime RAISE EXCEPTION TYPE cx_order_creation_failed. ENDIF.
COMMIT ENTITIES.ENDMETHOD.Domain Services implementieren
Domain Services kapseln Geschaeftslogik, die nicht zu einer einzelnen Entity gehoert:
" Interface fuer Domain ServiceINTERFACE 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.
" ImplementierungCLASS 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. " Zwischensumme berechnen DATA(lv_subtotal) = REDUCE decfloat34( INIT sum = CONV decfloat34( 0 ) FOR item IN it_items NEXT sum = sum + ( item-quantity * item-unit_price ) ).
" Kundenrabatt anwenden 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. " Rabattcode validieren und anwenden 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.Domain Service in RAP Action verwenden
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. " Order und Items laden 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).
" Domain Service instanziieren DATA(lo_pricing) = NEW zcl_pricing_service( ).
LOOP AT lt_orders INTO DATA(ls_order). " Items und Kundendaten holen DATA(lt_order_items) = FILTER #( lt_items WHERE OrderId = ls_order-OrderId ). DATA(ls_customer) = VALUE #( lt_customers[ CustomerId = ls_order-CustomerId ] OPTIONAL ).
" Domain Service aufrufen DATA(ls_pricing) = lo_pricing->zif_pricing_service~calculate_order_total( it_items = CORRESPONDING #( lt_order_items ) iv_customer_tier = ls_customer-CustomerTier ).
" Aggregate aktualisieren 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.
" Ergebnis zurueckgeben 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 signalisieren wichtige Geschaeftsereignisse und ermoeglichen lose Kopplung zwischen Bounded Contexts:
Events definieren
" Behavior Definition mit Eventsmanaged implementation in class zbp_i_order unique;
define behavior for ZI_Order alias Order{ " ... Standard-Operationen ...
" Domain Events deklarieren 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 als Struktur
" 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;}Events ausloesen
CLASS lhc_order IMPLEMENTATION. METHOD confirmOrder. " Status aendern 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.
" Aktuelle Daten lesen READ ENTITIES OF zi_order IN LOCAL MODE ENTITY Order ALL FIELDS WITH CORRESPONDING #( keys ) RESULT DATA(lt_orders).
" Domain Event ausloesen 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.
" Ergebnis result = CORRESPONDING #( lt_orders ). ENDMETHOD.ENDCLASS.Event Handler in anderem 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'. " Event-Daten auslesen DATA ls_event TYPE zs_order_confirmed_event. ls_event = CORRESPONDING #( is_event_data ).
" Shipment im Logistics Context erstellen 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
Ein zentrales DDD-Konzept ist die Ubiquitous Language - ein gemeinsames Vokabular fuer Fachbereich und Entwicklung:
CDS Naming Conventions
" Entity-Namen entsprechen Fachbegriffendefine root view entity ZI_SalesOrder " Kundenauftragdefine view entity ZI_SalesOrderItem " Auftragspositiondefine view entity ZI_DeliveryNote " Lieferscheindefine view entity ZI_Invoice " Rechnungdefine root view entity ZI_Customer " Kunde
" Feldnamen sind selbsterklaerend@EndUserText.label: 'Kundenauftrag'define root view entity ZI_SalesOrder{ key order_number as OrderNumber, " Auftragsnummer customer_number as CustomerNumber, " Kundennummer order_date as OrderDate, " Auftragsdatum requested_delivery as RequestedDelivery, " Wunschliefertermin net_value as NetValue, " Nettowert tax_amount as TaxAmount, " Steuerbetrag gross_value as GrossValue, " Bruttowert payment_terms as PaymentTerms, " Zahlungsbedingungen shipping_method as ShippingMethod, " Versandart order_status as OrderStatus " Auftragsstatus}Domain-spezifische Datentypen
" Eigene Datenelemente fuer Fachbegriffe@EndUserText.label: 'Auftragsnummer'@AbapCatalog.dataMaintenance: #ALLOWEDdefine type zdd_order_number : abap.numc(10);
@EndUserText.label: 'Kundennummer'define type zdd_customer_number : abap.numc(10);
@EndUserText.label: 'Auftragsstatus'define type zdd_order_status : abap.char(2);
" Verwendung in Tabelle und CDSdefine 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
Ein Anti-Corruption Layer schuetzt den eigenen Bounded Context vor fremden Datenmodellen:
" Interface zum externen SystemINTERFACE zif_external_product_service. METHODS get_product IMPORTING iv_external_id TYPE string RETURNING VALUE(rs_product) TYPE zs_external_product.ENDINTERFACE.
" Anti-Corruption LayerCLASS 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. " Externes Produkt abrufen 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.
" In eigenes Domaenenmodell uebersetzen rs_product = translate_product( ls_external ). ENDMETHOD.
METHOD translate_product. " Mapping auf eigene Struktur 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.
" Externe Kategorien auf interne mappen 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
Eine Context Map dokumentiert die Beziehungen zwischen 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 fuer DDD mit RAP
1. Aggregate-Grenzen richtig setzen
" GUT: Order ist Aggregate Root mit Items als Childrendefine root view entity ZI_Order composition [0..*] of ZI_OrderItem as _Items " Teil des Aggregates
" SCHLECHT: Zu grosses Aggregatedefine root view entity ZI_Order composition [0..*] of ZI_OrderItem as _Items composition [0..*] of ZI_Invoice as _Invoices " Eigenes Aggregate! composition [0..*] of ZI_Shipment as _Shipments " Eigenes Aggregate!2. Konsistenz innerhalb des Aggregates
" Determination aktualisiert abgeleitete Werte im Aggregatedetermination calculateTotals on modify { field Quantity, UnitPrice; " Bei Aenderung von Items}
METHOD calculateTotals. " Alle Items summieren DATA(lv_total) = REDUCE decfloat34( INIT sum = CONV decfloat34( 0 ) FOR item IN lt_items NEXT sum = sum + item-LineTotal ).
" Root Entity aktualisieren MODIFY ENTITIES OF zi_order IN LOCAL MODE ENTITY Order UPDATE FIELDS ( TotalAmount ) WITH VALUE #( ( %tky = ls_order-%tky TotalAmount = lv_total ) ).ENDMETHOD.3. Repositories abstrahieren
" Repository Interface fuer AggregateINTERFACE 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-basierte ImplementierungCLASS 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.
" Items laden READ ENTITIES OF zi_order ENTITY Order BY \_Items ALL FIELDS WITH VALUE #( ( OrderId = iv_order_id ) ) RESULT DATA(lt_items).
" Aggregate zusammensetzen rs_order-header = CORRESPONDING #( lt_orders[ 1 ] ). rs_order-items = CORRESPONDING #( lt_items ). ENDMETHOD.ENDCLASS.4. Fachliche Methoden statt technischer
" GUT: Fachliche Actionaction confirmOrder result [1] $self;action shipOrder parameter ZA_ShipmentDetails result [1] $self;action cancelOrder parameter ZA_CancellationReason result [1] $self;
" SCHLECHT: Technische Actionaction setStatusConfirmed result [1] $self;action updateShippingData parameter ZA_ShippingData result [1] $self;Weiterführende Themen
- RAP Grundlagen - Basis fuer Business Object Entwicklung
- Design Patterns fuer RAP - Factory, Strategy und mehr
- RAP Business Events - Event-Driven Architecture
- Clean ABAP - Code-Qualitaet und Naming Conventions
Fazit
Domain-Driven Design und RAP ergaenzen sich hervorragend:
- Aggregate = Business Object: Die RAP-Architektur mit Root und Child Entities entspricht direkt dem Aggregate-Konzept
- Bounded Contexts = Software Components: Package-Strukturen definieren klare Grenzen
- Domain Events = RAP Business Events: Lose Kopplung zwischen Kontexten
- Repository = EML/RAP Runtime: Persistenz wird vom Framework abstrahiert
DDD erfordert anfangs mehr Analyse, zahlt sich aber durch bessere Wartbarkeit, klarere Kommunikation zwischen Fachbereich und Entwicklung sowie flexiblere Architektur aus. RAP bietet die technische Grundlage, um diese Konzepte elegant umzusetzen.