Pattern CQRS dans ABAP Cloud : Séparer optimalement les opérations de lecture et d écriture

Catégorie
Best Practices
Publié
Auteur
Johannes

CQRS (Command Query Responsibility Segregation) est un pattern architectural qui sépare strictement les opérations de lecture et d’écriture. Dans ABAP Cloud, CQRS permet des systèmes hautement optimisés où les chemins Query et Command peuvent être mis à l’échelle et optimisés indépendamment.

Qu’est-ce que CQRS ?

CQRS est basé sur le principe de Command Query Separation (CQS), mais l’étend au niveau architectural :

┌─────────────────────────────────────────────────────────────────────────────┐
│ Architecture CQRS │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌───────────────────┐ │
│ │ Client │ │
│ └─────────┬─────────┘ │
│ │ │
│ ┌─────────────┴─────────────┐ │
│ │ │ │
│ ▼ ▼ │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ API Command │ │ API Query │ │
│ │ (Écriture) │ │ (Lecture) │ │
│ └────────┬────────┘ └────────┬────────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ Modèle Command │ │ Modèle Query │ │
│ │ (Validation, │ Event │ (Optimisé │ │
│ │ Logique │ ──────> │ pour lecture, │ │
│ │ métier) │ │ Dénormalisé) │ │
│ └────────┬────────┘ └────────┬────────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ Write Store │ │ Read Store │ │
│ │ (Normalisé) │ │ (Dénormalisé) │ │
│ └─────────────────┘ └─────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘

Principes fondamentaux

PrincipeDescription
SéparationLes modèles de lecture et d’écriture sont complètement séparés
OptimisationChaque modèle est optimisé pour son objectif
ÉvolutivitéCôtés Query et Command évolutifs indépendamment
FlexibilitéStockages différents possibles pour lecture et écriture

Quand CQRS est-il pertinent ?

CQRS ne vaut pas pour chaque projet. Voici des critères de décision :

CQRS recommandé

ScénarioRaison
Taux de lecture élevé (>90% Reads)L’optimisation Query apporte le plus grand bénéfice
Modèles de lecture complexesViews agrégées et dénormalisées pertinentes
Mise à l’échelle différenciéeCharges de lecture et d’écriture très différentes
Event Sourcing prévuCQRS est un complément naturel
Logique de domaine complexeLe côté Command peut rester focalisé

CQRS non recommandé

ScénarioRaison
Applications CRUD simplesL’overhead dépasse le bénéfice
Charge lecture/écriture équilibréePas de direction d’optimisation claire
Exigence de cohérence forteEventual Consistency problématique
Petite équipeComplexité supplémentaire difficile à maintenir

CQRS dans ABAP Cloud

Dans ABAP Cloud, CQRS peut être implémenté élégamment avec RAP. Le Modèle Command correspond au BO RAP transactionnel, tandis que le Modèle Query est représenté par des CDS Views optimisées.

Vue d’ensemble de l’architecture

┌─────────────────────────────────────────────────────────────────────────────┐
│ CQRS dans ABAP Cloud │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ App Fiori │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │ │ │
│ │ Mutations │ Queries │
│ ▼ ▼ │
│ ┌─────────────────────────────┐ ┌──────────────────────────────────┐ │
│ │ Service Transactionnel │ │ Service Analytique │ │
│ │ (API Command) │ │ (API Query) │ │
│ │ │ │ │ │
│ │ ┌───────────────────────┐ │ │ ┌────────────────────────────┐ │ │
│ │ │ ZC_Order (Projection) │ │ │ │ ZC_OrderAnalytics (Read) │ │ │
│ │ │ - create, update │ │ │ │ - Agrégations │ │ │
│ │ │ - delete, actions │ │ │ │ - Joins, Calculs │ │ │
│ │ └───────────────────────┘ │ │ └────────────────────────────┘ │ │
│ │ │ │ │ │ │ │
│ │ ▼ │ │ │ │ │
│ │ ┌───────────────────────┐ │ │ │ │ │
│ │ │ ZI_Order (Interface) │ │ │ │ │ │
│ │ │ + Behavior Definition │ │ │ │ │ │
│ │ └───────────────────────┘ │ │ │ │ │
│ │ │ │ │ │ │ │
│ └─────────────┼───────────────┘ └───────────────┼──────────────────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ Tables de base de données │ │
│ │ (Normalisées, Single Source of Truth) │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘

Modèle Query (Optimisé pour la lecture)

Le modèle Query dans ABAP Cloud se compose de CDS Views optimisées pour des accès en lecture rapides.

Base : Vue de lecture dénormalisée

@EndUserText.label: 'Commandes - Modèle Query"
@AbapCatalog.viewEnhancementCategory: [#NONE]
@AccessControl.authorizationCheck: #CHECK
@Metadata.ignorePropagatedAnnotations: true
@ObjectModel.usageType: {
serviceQuality: #A,
sizeCategory: #L,
dataClass: #TRANSACTIONAL
}
define view entity ZI_OrderQuery
as select from zorder as Order
-- Dénormalisation : Données client directement intégrées
inner join zcustomer as Customer
on Order.customer_id = Customer.customer_id
-- Dénormalisation : Master data matériel
left outer join zmaterial as Material
on Order.material_id = Material.material_id
{
key Order.order_id as OrderId,
Order.order_date as OrderDate,
Order.status as Status,
-- Données client (dénormalisées)
Customer.customer_id as CustomerId,
Customer.customer_name as CustomerName,
Customer.city as CustomerCity,
Customer.country as CustomerCountry,
Customer.credit_limit as CustomerCreditLimit,
-- Données matériel (dénormalisées)
Material.material_id as MaterialId,
Material.material_text as MaterialText,
Material.material_group as MaterialGroup,
-- Champs calculés
Order.quantity as Quantity,
Order.unit_price as UnitPrice,
Order.quantity * Order.unit_price as TotalAmount,
-- Texte de statut calculé
case Order.status
when 'N' then 'Nouveau"
when 'P' then 'En traitement"
when 'C' then 'Terminé"
when 'X' then 'Annulé"
else 'Inconnu"
end as StatusText,
-- Classification temporelle
case
when Order.order_date >= $session.system_date then 'Aujourd hui"
when Order.order_date >= $session.system_date - 7 then 'Cette semaine"
when Order.order_date >= $session.system_date - 30 then 'Ce mois"
else 'Plus ancien"
end as TimeCategory
}

Vue d’analyse agrégée

@EndUserText.label: 'Commandes - Analyse agrégée"
@Analytics.dataCategory: #CUBE
define view entity ZI_OrderAnalytics
as select from ZI_OrderQuery
{
-- Dimensions
@AnalyticsDetails.query.axis: #ROWS
CustomerCountry,
@AnalyticsDetails.query.axis: #ROWS
MaterialGroup,
@AnalyticsDetails.query.axis: #ROWS
TimeCategory,
@AnalyticsDetails.query.axis: #ROWS
Status,
-- Mesures
@Aggregation.default: #SUM
TotalAmount,
@Aggregation.default: #SUM
Quantity,
@Aggregation.default: #COUNT
@EndUserText.label: 'Nombre de commandes"
cast(1 as abap.int4) as OrderCount,
@Aggregation.default: #AVG
@EndUserText.label: 'Valeur moyenne de commande"
TotalAmount as AvgOrderValue
}

Définition du service Query

@EndUserText.label: 'Service Query Commandes"
@ObjectModel.query.implementedBy: 'ABAP:ZCL_ORDER_QUERY_PROVIDER"
define custom entity ZCE_OrderQuery
{
key OrderId : abap.char(10);
OrderDate : abap.dats;
CustomerName : abap.char(80);
CustomerCountry: land1;
MaterialText : abap.char(40);
TotalAmount : abap.dec(15,2);
StatusText : abap.char(20);
-- Champs Query étendus
CustomerRanking: abap.int4;
DaysOpen : abap.int4;
}

Implémentation du Query Provider

CLASS zcl_order_query_provider DEFINITION
PUBLIC FINAL
CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES if_rap_query_provider.
ENDCLASS.
CLASS zcl_order_query_provider IMPLEMENTATION.
METHOD if_rap_query_provider~select.
" Pagination et tri depuis la requête
DATA(lv_top) = io_request->get_paging( )->get_page_size( ).
DATA(lv_skip) = io_request->get_paging( )->get_offset( ).
DATA(lt_sort) = io_request->get_sort_elements( ).
DATA(lt_filter) = io_request->get_filter( )->get_as_ranges( ).
" Construire une requête optimisée
SELECT
order~order_id AS orderid,
order~order_date AS orderdate,
cust~customer_name AS customername,
cust~country AS customercountry,
mat~material_text AS materialtext,
order~quantity * order~unit_price AS totalamount,
CASE order~status
WHEN 'N' THEN 'Nouveau"
WHEN 'P' THEN 'En traitement"
WHEN 'C' THEN 'Terminé"
ELSE 'Inconnu"
END AS statustext,
-- Classement basé sur le chiffre d'affaires
DENSE_RANK( ) OVER( ORDER BY order~quantity * order~unit_price DESC )
AS customerranking,
DATS_DAYS_BETWEEN( order~order_date, @sy-datum ) AS daysopen
FROM zorder AS order
INNER JOIN zcustomer AS cust ON order~customer_id = cust~customer_id
LEFT OUTER JOIN zmaterial AS mat ON order~material_id = mat~material_id
INTO TABLE @DATA(lt_result)
UP TO @lv_top ROWS
OFFSET @lv_skip.
" Retourner le résultat
io_response->set_data( lt_result ).
io_response->set_total_number_of_records( lines( lt_result ) ).
ENDMETHOD.
ENDCLASS.

Modèle Command (Optimisé pour l’écriture)

Le modèle Command se concentre sur la logique métier, les validations et l’intégrité transactionnelle.

Vue Interface (Normalisée)

@EndUserText.label: 'Commande - Modèle Command"
@AccessControl.authorizationCheck: #CHECK
define view entity ZI_Order
as select from zorder
{
key order_id as OrderId,
customer_id as CustomerId,
material_id as MaterialId,
order_date as OrderDate,
quantity as Quantity,
unit_price as UnitPrice,
status as Status,
created_by as CreatedBy,
created_at as CreatedAt,
changed_by as ChangedBy,
changed_at as ChangedAt,
-- Associations (pas de dénormalisation)
_Customer,
_Material,
_Items
}

Behavior Definition (Commands)

managed implementation in class zbp_i_order unique;
strict ( 2 );
with draft;
define behavior for ZI_Order alias Order
persistent table zorder
draft table zorder_d
lock master total etag ChangedAt
authorization master ( instance )
etag master ChangedAt
{
// Commands standard
create;
update;
delete;
// Contrôle des champs
field ( readonly ) OrderId, CreatedBy, CreatedAt, ChangedBy, ChangedAt;
field ( mandatory ) CustomerId, MaterialId, Quantity;
// Actions métier (Commands)
action confirm result [1] $self;
action cancel result [1] $self;
action reopen result [1] $self;
// Factory Action
factory action copyOrder [1];
// Validations
validation validateCustomer on save { create; update; field CustomerId; }
validation validateQuantity on save { create; update; field Quantity; }
validation validateStatus on save { field Status; }
// Determinations
determination calculatePrice on modify { field Quantity, MaterialId; }
determination setInitialStatus on modify { create; }
// Events pour la synchronisation Query
event orderCreated;
event orderConfirmed;
event orderCancelled;
event orderChanged;
draft action Edit;
draft action Activate;
draft action Discard;
draft action Resume;
draft determine action Prepare;
}

Implémentation Behavior

CLASS zbp_i_order DEFINITION LOCAL.
ENDCLASS.
CLASS lhc_order DEFINITION INHERITING FROM cl_abap_behavior_handler.
PRIVATE SECTION.
METHODS get_instance_authorizations FOR INSTANCE AUTHORIZATION
IMPORTING keys REQUEST requested_authorizations FOR Order RESULT result.
METHODS validateCustomer FOR VALIDATE ON SAVE
IMPORTING keys FOR Order~validateCustomer.
METHODS validateQuantity FOR VALIDATE ON SAVE
IMPORTING keys FOR Order~validateQuantity.
METHODS confirm FOR MODIFY
IMPORTING keys FOR ACTION Order~confirm RESULT result.
METHODS cancel FOR MODIFY
IMPORTING keys FOR ACTION Order~cancel RESULT result.
METHODS calculatePrice FOR DETERMINE ON MODIFY
IMPORTING keys FOR Order~calculatePrice.
METHODS setInitialStatus FOR DETERMINE ON MODIFY
IMPORTING keys FOR Order~setInitialStatus.
ENDCLASS.
CLASS lhc_order IMPLEMENTATION.
METHOD get_instance_authorizations.
ENDMETHOD.
METHOD validateCustomer.
" Lire les commandes concernées
READ ENTITIES OF zi_order IN LOCAL MODE
ENTITY Order
FIELDS ( CustomerId )
WITH CORRESPONDING #( keys )
RESULT DATA(lt_orders).
" Vérifier les clients
SELECT customer_id, credit_limit, blocked
FROM zcustomer
FOR ALL ENTRIES IN @lt_orders
WHERE customer_id = @lt_orders-CustomerId
INTO TABLE @DATA(lt_customers).
LOOP AT lt_orders INTO DATA(ls_order).
READ TABLE lt_customers INTO DATA(ls_customer)
WITH KEY customer_id = ls_order-CustomerId.
IF sy-subrc <> 0.
APPEND VALUE #(
%tky = ls_order-%tky
%msg = new_message_with_text(
severity = if_abap_behv_message=>severity-error
text = |Le client { ls_order-CustomerId } n existe pas| )
) TO reported-order.
APPEND VALUE #( %tky = ls_order-%tky ) TO failed-order.
CONTINUE.
ENDIF.
IF ls_customer-blocked = abap_true.
APPEND VALUE #(
%tky = ls_order-%tky
%msg = new_message_with_text(
severity = if_abap_behv_message=>severity-error
text = |Le client { ls_order-CustomerId } est bloqué| )
) TO reported-order.
APPEND VALUE #( %tky = ls_order-%tky ) TO failed-order.
ENDIF.
ENDLOOP.
ENDMETHOD.
METHOD validateQuantity.
READ ENTITIES OF zi_order IN LOCAL MODE
ENTITY Order
FIELDS ( Quantity )
WITH CORRESPONDING #( keys )
RESULT DATA(lt_orders).
LOOP AT lt_orders INTO DATA(ls_order) WHERE Quantity <= 0.
APPEND VALUE #(
%tky = ls_order-%tky
%msg = new_message_with_text(
severity = if_abap_behv_message=>severity-error
text = |La quantité doit être supérieure à 0| )
%element-Quantity = if_abap_behv=>mk-on
) TO reported-order.
APPEND VALUE #( %tky = ls_order-%tky ) TO failed-order.
ENDLOOP.
ENDMETHOD.
METHOD confirm.
" Lire les commandes
READ ENTITIES OF zi_order IN LOCAL MODE
ENTITY Order
ALL FIELDS WITH CORRESPONDING #( keys )
RESULT DATA(lt_orders).
LOOP AT lt_orders INTO DATA(ls_order).
" Vérifier si la confirmation est possible
IF ls_order-Status <> 'N'.
APPEND VALUE #(
%tky = ls_order-%tky
%msg = new_message_with_text(
severity = if_abap_behv_message=>severity-error
text = |Seules les nouvelles commandes peuvent être confirmées| )
) TO reported-order.
APPEND VALUE #( %tky = ls_order-%tky ) TO failed-order.
CONTINUE.
ENDIF.
" Modifier le statut
MODIFY ENTITIES OF zi_order IN LOCAL MODE
ENTITY Order
UPDATE FIELDS ( Status )
WITH VALUE #( (
%tky = ls_order-%tky
Status = 'C' " Confirmé
) )
REPORTED DATA(lt_update_reported)
FAILED DATA(lt_update_failed).
" Déclencher l'événement pour la synchronisation Query
RAISE ENTITY EVENT zi_order~orderConfirmed
FROM VALUE #( ( OrderId = ls_order-OrderId ) ).
ENDLOOP.
" Retourner le résultat
READ ENTITIES OF zi_order IN LOCAL MODE
ENTITY Order
ALL FIELDS WITH CORRESPONDING #( keys )
RESULT DATA(lt_result).
result = VALUE #( FOR order IN lt_result (
%tky = order-%tky
%param = order
) ).
ENDMETHOD.
METHOD cancel.
READ ENTITIES OF zi_order IN LOCAL MODE
ENTITY Order
ALL FIELDS WITH CORRESPONDING #( keys )
RESULT DATA(lt_orders).
LOOP AT lt_orders INTO DATA(ls_order).
IF ls_order-Status = 'X'.
APPEND VALUE #(
%tky = ls_order-%tky
%msg = new_message_with_text(
severity = if_abap_behv_message=>severity-warning
text = |Commande déjà annulée| )
) TO reported-order.
CONTINUE.
ENDIF.
MODIFY ENTITIES OF zi_order IN LOCAL MODE
ENTITY Order
UPDATE FIELDS ( Status )
WITH VALUE #( (
%tky = ls_order-%tky
Status = 'X"
) )
REPORTED DATA(lt_update_reported).
RAISE ENTITY EVENT zi_order~orderCancelled
FROM VALUE #( ( OrderId = ls_order-OrderId ) ).
ENDLOOP.
READ ENTITIES OF zi_order IN LOCAL MODE
ENTITY Order
ALL FIELDS WITH CORRESPONDING #( keys )
RESULT DATA(lt_result).
result = VALUE #( FOR order IN lt_result (
%tky = order-%tky
%param = order
) ).
ENDMETHOD.
METHOD calculatePrice.
READ ENTITIES OF zi_order IN LOCAL MODE
ENTITY Order
FIELDS ( MaterialId Quantity )
WITH CORRESPONDING #( keys )
RESULT DATA(lt_orders).
" Récupérer les prix du master data matériel
SELECT material_id, standard_price
FROM zmaterial
FOR ALL ENTRIES IN @lt_orders
WHERE material_id = @lt_orders-MaterialId
INTO TABLE @DATA(lt_prices).
LOOP AT lt_orders INTO DATA(ls_order).
READ TABLE lt_prices INTO DATA(ls_price)
WITH KEY material_id = ls_order-MaterialId.
IF sy-subrc = 0.
MODIFY ENTITIES OF zi_order IN LOCAL MODE
ENTITY Order
UPDATE FIELDS ( UnitPrice )
WITH VALUE #( (
%tky = ls_order-%tky
UnitPrice = ls_price-standard_price
) ).
ENDIF.
ENDLOOP.
ENDMETHOD.
METHOD setInitialStatus.
MODIFY ENTITIES OF zi_order IN LOCAL MODE
ENTITY Order
UPDATE FIELDS ( Status )
WITH VALUE #( FOR key IN keys (
%tky = key-%tky
Status = 'N' " Nouveau
) ).
ENDMETHOD.
ENDCLASS.

Synchronisation basée sur les événements

Dans une architecture CQRS complète, les modèles Query et Command sont synchronisés par des événements.

Event Handler pour la mise à jour Query

CLASS zcl_order_event_handler DEFINITION
PUBLIC FINAL
CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES if_rap_event_handler.
ENDCLASS.
CLASS zcl_order_event_handler IMPLEMENTATION.
METHOD if_rap_event_handler~handle.
" Dispatch basé sur le type d'événement
CASE io_event->get_event( ).
WHEN 'ORDERCREATED'.
handle_order_created( io_event ).
WHEN 'ORDERCONFIRMED'.
handle_order_confirmed( io_event ).
WHEN 'ORDERCANCELLED'.
handle_order_cancelled( io_event ).
WHEN 'ORDERCHANGED'.
handle_order_changed( io_event ).
ENDCASE.
ENDMETHOD.
METHOD handle_order_created.
" Mettre à jour le cache Query ou synchroniser le Read-Store
DATA(lt_event_data) = io_event->get_converted_event_data( ).
" Pour un Read-Store séparé : Écrire les données dénormalisées
" Pour une solution cache : Invalider le cache
LOOP AT lt_event_data INTO DATA(ls_event).
" Récupérer les données complètes de la commande
SELECT SINGLE *
FROM zi_orderquery
WHERE orderid = @ls_event-orderid
INTO @DATA(ls_order_query).
" Écrire dans le Read-Store (si table séparée)
" MODIFY zorder_read FROM ls_order_query.
" Ou : Invalider le cache
" zcl_order_cache=>invalidate( ls_event-orderid ).
ENDLOOP.
ENDMETHOD.
METHOD handle_order_confirmed.
" Mettre à jour le statut dans le Read-Store
ENDMETHOD.
METHOD handle_order_cancelled.
" Mettre à jour le statut dans le Read-Store
ENDMETHOD.
METHOD handle_order_changed.
" Mettre à jour les champs modifiés dans le Read-Store
ENDMETHOD.
ENDCLASS.

Intégration avec RAP

La séparation CQRS dans RAP s’effectue via des services séparés pour la lecture et l’écriture.

Service Transactionnel (Commands)

@EndUserText.label: 'Service de gestion des commandes"
define service ZUI_ORDER_O4 {
expose ZC_Order as Order;
expose ZC_OrderItem as OrderItem;
}

Service Analytics (Queries)

@EndUserText.label: 'Service Analytics Commandes"
define service ZUI_ORDER_ANALYTICS_O4 {
expose ZC_OrderAnalytics as OrderAnalytics;
expose ZC_OrderQuery as OrderQuery;
}

Séparation des Service Bindings

Dans les Service Bindings, l’objectif est explicitement défini :

  • Service Transactionnel : OData V4 avec CRUD complet
  • Service Analytics : OData V4 Analytics (lecture seule)

Bonnes pratiques

1. Responsabilités claires

" Command Handler - focalisé sur la logique métier
CLASS zcl_order_command_handler DEFINITION.
PUBLIC SECTION.
METHODS create_order
IMPORTING is_order TYPE zif_order_types=>ts_create_command
RAISING zcx_order_validation.
METHODS confirm_order
IMPORTING iv_order_id TYPE zorder-order_id
RAISING zcx_order_state.
ENDCLASS.
" Query Handler - focalisé sur la récupération de données
CLASS zcl_order_query_handler DEFINITION.
PUBLIC SECTION.
METHODS get_orders_by_customer
IMPORTING iv_customer_id TYPE zcustomer-customer_id
RETURNING VALUE(rt_orders) TYPE zif_order_types=>tt_order_summary.
METHODS get_order_statistics
IMPORTING iv_date_from TYPE datum
iv_date_to TYPE datum
RETURNING VALUE(rs_stats) TYPE zif_order_types=>ts_statistics.
ENDCLASS.

2. Prendre en compte l’Eventual Consistency

" Query avec indication de cohérence
METHOD get_order_status.
" Note : Les données peuvent être retardées jusqu'à X secondes
SELECT SINGLE status, last_sync_timestamp
FROM zorder_read
WHERE order_id = @iv_order_id
INTO @DATA(ls_result).
" Pour les requêtes critiques : Lire directement depuis le Command-Store
IF iv_require_consistent = abap_true.
SELECT SINGLE status
FROM zorder
WHERE order_id = @iv_order_id
INTO @DATA(lv_status).
ls_result-status = lv_status.
ENDIF.
rs_result = ls_result.
ENDMETHOD.

3. Modèles de données séparés

AspectModèle CommandModèle Query
Normalisation3NF, normaliséDénormalisé
OptimisationPerformance écriturePerformance lecture
IndexOptimisés écritureOptimisés lecture
ValidationComplèteAucune
Logique métierOuiNon

4. Commands idempotents

METHOD execute_command.
" Vérifier si le command a déjà été exécuté
SELECT SINGLE @abap_true
FROM zcommand_log
WHERE command_id = @is_command-command_id
INTO @DATA(lv_exists).
IF lv_exists = abap_true.
" Idempotent : Déjà exécuté, pas d'erreur
RETURN.
ENDIF.
" Exécuter le command
" ...
" Logger
INSERT zcommand_log FROM @( VALUE #(
command_id = is_command-command_id
executed_at = utclong_current( )
executed_by = sy-uname
) ).
ENDMETHOD.

Résumé

AspectDescription
Modèle QueryCDS Views, dénormalisé, optimisé lecture
Modèle CommandRAP BO avec Behavior Definition, normalisé
SynchronisationRAP Business Events ou jobs en arrière-plan
CohérenceAccepter l’Eventual Consistency
Mise à l’échelleServices séparés pour lecture/écriture

CQRS dans ABAP Cloud permet des systèmes hautement optimisés pour des exigences complexes. La séparation des modèles de lecture et d’écriture offre de la flexibilité pour l’optimisation et la mise à l’échelle. Cependant, le pattern nécessite une complexité supplémentaire et ne devrait être utilisé qu’en cas de bénéfice clair.

Sujets connexes