Le Domain-Driven Design (DDD) est une approche de developpement logiciel qui place le domaine metier au centre. RAP (RESTful ABAP Programming) offre des conditions ideales pour mettre en oeuvre les principes DDD : les Business Objects, Entities et Compositions refletent directement les concepts DDD.
Pourquoi DDD avec RAP ?
| Concept DDD | Implementation RAP | Avantage |
|---|---|---|
| Bounded Context | Software Component / Package | Delimitation claire des domaines metier |
| Aggregate | Business Object (Root Entity) | Limites de coherence definies |
| Entity | CDS View Entity | Identite et cycle de vie |
| Value Object | Structure CDS / Embedded View | Objets valeur immuables |
| Domain Event | RAP Business Event | Couplage lache entre contextes |
| Repository | RAP Runtime (EML) | Abstraction de la persistance |
Concepts de base DDD
Bounded Context
Un Bounded Context est une frontiere logique a l’interieur de laquelle un modele de domaine specifique s’applique. Dans ABAP Cloud, cela correspond generalement a une Software Component ou une arborescence de packages.
┌─────────────────────────────────────────────────────────────┐│ ENTREPRISE ││ ┌─────────────────────┐ ┌─────────────────────────────┐ ││ │ CONTEXTE VENTES │ │ CONTEXTE LOGISTIQUE │ ││ │ ┌───────────────┐ │ │ ┌───────────────────────┐ │ ││ │ │ Order (BO) │ │ │ │ Shipment (BO) │ │ ││ │ │ Customer │──┼────┼──│ DeliveryAddress │ │ ││ │ │ OrderItem │ │ │ │ ShipmentItem │ │ ││ │ └───────────────┘ │ │ └───────────────────────┘ │ ││ │ ┌───────────────┐ │ │ ┌───────────────────────┐ │ ││ │ │ Product (ref) │◄─┼────┼──│ Inventory (BO) │ │ ││ │ └───────────────┘ │ │ │ StockLevel │ │ ││ └─────────────────────┘ │ └───────────────────────┘ │ ││ └─────────────────────────────┘ │└─────────────────────────────────────────────────────────────┘Aggregate et Root Entity
Un Aggregate est un groupe d’objets lies avec une Root Entity. Dans RAP, la Root Entity correspond au Business Object avec 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,
" Champs administratifs @Semantics.user.createdBy: true created_by as CreatedBy, @Semantics.systemDateTime.createdAt: true created_at as CreatedAt,
" Associations _Items, _Customer}Child Entity (partie de l’Aggregate)
Les Entities a l’interieur d’un Aggregate ne sont accessibles que via la Root Entity :
" CHILD ENTITY - Partie de l'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,
" Champ calcule quantity * unit_price as LineTotal,
_Order, _Product}Value Object
Les Value Objects n’ont pas d’identite propre et sont definis par leurs attributs. Dans RAP, ils sont modelises comme des structures imbriquees ou des champs calcules :
" Value Object comme Structure CDS@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;}
" Utilisation dans une 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}Implementer les regles d’Aggregate dans RAP
Regle 1 : Acces uniquement via Aggregate Root
La Behavior Definition garantit que les Child Entities ne sont manipulees que via la Root Entity :
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;
" Les Items sont crees/modifies via Order association _Items { create; }
" Assurer les invariants de l'Aggregate 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;
" Pas de creation directe - uniquement via Order field ( readonly ) OrderId;
association _Order;}Regle 2 : Proteger les invariants
Les invariants de l’Aggregate sont verifies dans les 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. " Verifier les invariants de l'Aggregate READ ENTITIES OF zi_order IN LOCAL MODE ENTITY Order ALL FIELDS WITH CORRESPONDING #( keys ) RESULT DATA(lt_orders).
" Charger les 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 : Au moins un Item requis 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 = 'La commande doit contenir au moins une position" ) ) TO reported-order. APPEND VALUE #( %tky = ls_order-%tky ) TO failed-order. CONTINUE. ENDIF.
" Invariant 2 : Le montant total doit correspondre aux 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 = 'Le montant total ne correspond pas aux positions" ) ) TO reported-order. APPEND VALUE #( %tky = ls_order-%tky ) TO failed-order. ENDIF. ENDLOOP. ENDMETHOD.ENDCLASS.Regle 3 : Coherence transactionnelle
Un Aggregate est toujours sauvegarde comme un tout :
METHOD create_order_with_items. " Deep Insert - Creer l'Aggregate en entier 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).
" Tout ou rien - transactionnel IF lt_failed IS NOT INITIAL. " Rollback automatique par RAP Runtime RAISE EXCEPTION TYPE cx_order_creation_failed. ENDIF.
COMMIT ENTITIES.ENDMETHOD.Implementer des Domain Services
Les Domain Services encapsulent la logique metier qui n’appartient pas a une seule Entity :
" Interface pour 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.
" ImplementationCLASS 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. " Calculer le sous-total DATA(lv_subtotal) = REDUCE decfloat34( INIT sum = CONV decfloat34( 0 ) FOR item IN it_items NEXT sum = sum + ( item-quantity * item-unit_price ) ).
" Appliquer la remise client 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. " Valider et appliquer le code de remise 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.Utiliser un Domain Service dans une 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. " Charger Order et 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).
" Instancier le Domain Service DATA(lo_pricing) = NEW zcl_pricing_service( ).
LOOP AT lt_orders INTO DATA(ls_order). " Recuperer les Items et les donnees client DATA(lt_order_items) = FILTER #( lt_items WHERE OrderId = ls_order-OrderId ). DATA(ls_customer) = VALUE #( lt_customers[ CustomerId = ls_order-CustomerId ] OPTIONAL ).
" Appeler le 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 ).
" Mettre a jour l'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.
" Retourner le resultat READ ENTITIES OF zi_order IN LOCAL MODE ENTITY Order ALL FIELDS WITH CORRESPONDING #( keys ) RESULT result. ENDMETHOD.ENDCLASS.Domain Events dans RAP
Les Domain Events signalent des evenements metier importants et permettent un couplage lache entre les Bounded Contexts :
Definir des Events
" Behavior Definition avec Eventsmanaged implementation in class zbp_i_order unique;
define behavior for ZI_Order alias Order{ " ... Operations standard ...
" Declarer les 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;}Parametre d’Event comme structure
" Definition du payload de l'Event@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;}Declencher des Events
CLASS lhc_order IMPLEMENTATION. METHOD confirmOrder. " Changer le statut 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.
" Lire les donnees actuelles READ ENTITIES OF zi_order IN LOCAL MODE ENTITY Order ALL FIELDS WITH CORRESPONDING #( keys ) RESULT DATA(lt_orders).
" Declencher le 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.
" Resultat result = CORRESPONDING #( lt_orders ). ENDMETHOD.ENDCLASS.Event Handler dans un autre 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'. " Lire les donnees de l'Event DATA ls_event TYPE zs_order_confirmed_event. ls_event = CORRESPONDING #( is_event_data ).
" Creer un Shipment dans le contexte Logistics 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 concept central du DDD est le Ubiquitous Language - un vocabulaire commun pour le domaine metier et le developpement :
Conventions de nommage CDS
" Les noms d'Entity correspondent aux termes metierdefine root view entity ZI_SalesOrder " Commande clientdefine view entity ZI_SalesOrderItem " Position de commandedefine view entity ZI_DeliveryNote " Bon de livraisondefine view entity ZI_Invoice " Facturedefine root view entity ZI_Customer " Client
" Les noms de champs sont explicites@EndUserText.label: 'Commande client"define root view entity ZI_SalesOrder{ key order_number as OrderNumber, " Numero de commande customer_number as CustomerNumber, " Numero client order_date as OrderDate, " Date de commande requested_delivery as RequestedDelivery, " Date de livraison souhaitee net_value as NetValue, " Valeur nette tax_amount as TaxAmount, " Montant TVA gross_value as GrossValue, " Valeur brute payment_terms as PaymentTerms, " Conditions de paiement shipping_method as ShippingMethod, " Mode d'expedition order_status as OrderStatus " Statut de commande}Types de donnees specifiques au domaine
" Elements de donnees propres pour les termes metier@EndUserText.label: 'Numero de commande"@AbapCatalog.dataMaintenance: #ALLOWEDdefine type zdd_order_number : abap.numc(10);
@EndUserText.label: 'Numero client"define type zdd_customer_number : abap.numc(10);
@EndUserText.label: 'Statut de commande"define type zdd_order_status : abap.char(2);
" Utilisation dans une table et 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;}Integration des Bounded Contexts
Anti-Corruption Layer
Un Anti-Corruption Layer protege son propre Bounded Context des modeles de donnees etrangers :
" Interface vers le systeme externeINTERFACE 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. " Recuperer le produit externe 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.
" Traduire vers notre propre modele de domaine rs_product = translate_product( ls_external ). ENDMETHOD.
METHOD translate_product. " Mapping vers notre propre 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.
" Mapper les categories externes vers les internes 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
Une Context Map documente les relations entre les Bounded Contexts :
┌─────────────────────────────────────────────────────────────────┐│ CONTEXT MAP │├─────────────────────────────────────────────────────────────────┤│ ││ ┌──────────────┐ Events ┌──────────────────┐ ││ │ VENTES │ ──────────────────────► │ LOGISTIQUE │ ││ │ Context │ OrderConfirmed │ Context │ ││ │ │ OrderCancelled │ │ ││ │ [Upstream] │ │ [Downstream] │ ││ └──────────────┘ └──────────────────┘ ││ │ │ ││ │ API Call │ ││ ▼ │ ││ ┌──────────────┐ │ ││ │ PRODUIT │ ◄────────────────────────────────┘ ││ │ Context │ ACL (Read-Only) ││ │ │ ││ │ [Upstream] │ ││ └──────────────┘ ││ │ ││ │ Conformist ││ ▼ ││ ┌──────────────┐ ││ │ EXTERNE │ ││ │ Catalog │ ││ │ (SAP S/4) │ ││ └──────────────┘ │└─────────────────────────────────────────────────────────────────┘Bonnes pratiques pour DDD avec RAP
1. Definir correctement les limites d’Aggregate
" BON : Order est Aggregate Root avec Items comme Childrendefine root view entity ZI_Order composition [0..*] of ZI_OrderItem as _Items " Partie de l'Aggregate
" MAUVAIS : Aggregate trop granddefine root view entity ZI_Order composition [0..*] of ZI_OrderItem as _Items composition [0..*] of ZI_Invoice as _Invoices " Aggregate propre ! composition [0..*] of ZI_Shipment as _Shipments " Aggregate propre !2. Coherence au sein de l’Aggregate
" La Determination met a jour les valeurs derivees dans l'Aggregatedetermination calculateTotals on modify { field Quantity, UnitPrice; " Lors de la modification des Items}
METHOD calculateTotals. " Sommer tous les Items DATA(lv_total) = REDUCE decfloat34( INIT sum = CONV decfloat34( 0 ) FOR item IN lt_items NEXT sum = sum + item-LineTotal ).
" Mettre a jour la 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. Abstraire les Repositories
" Interface Repository pour 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.
" Implementation basee sur RAPCLASS 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.
" Charger les Items READ ENTITIES OF zi_order ENTITY Order BY \_Items ALL FIELDS WITH VALUE #( ( OrderId = iv_order_id ) ) RESULT DATA(lt_items).
" Assembler l'Aggregate rs_order-header = CORRESPONDING #( lt_orders[ 1 ] ). rs_order-items = CORRESPONDING #( lt_items ). ENDMETHOD.ENDCLASS.4. Methodes metier au lieu de techniques
" BON : Action metieraction confirmOrder result [1] $self;action shipOrder parameter ZA_ShipmentDetails result [1] $self;action cancelOrder parameter ZA_CancellationReason result [1] $self;
" MAUVAIS : Action techniqueaction setStatusConfirmed result [1] $self;action updateShippingData parameter ZA_ShippingData result [1] $self;Sujets connexes
- RAP Grundlagen - Base pour le developpement de Business Objects
- Design Patterns pour RAP - Factory, Strategy et plus
- RAP Business Events - Architecture Event-Driven
- Clean ABAP - Qualite du code et conventions de nommage
Conclusion
Domain-Driven Design et RAP se completent parfaitement :
- Aggregate = Business Object : l’architecture RAP avec Root et Child Entities correspond directement au concept d’Aggregate
- Bounded Contexts = Software Components : les structures de packages definissent des limites claires
- Domain Events = RAP Business Events : couplage lache entre les contextes
- Repository = EML/RAP Runtime : la persistance est abstraite par le framework
Le DDD necessite plus d’analyse au depart, mais cela est payant par une meilleure maintenabilite, une communication plus claire entre le domaine metier et le developpement, ainsi qu’une architecture plus flexible. RAP fournit la base technique pour mettre en oeuvre ces concepts avec elegance.