CQRS (Command Query Responsibility Segregation) ist ein Architekturmuster, das Lese- und Schreiboperationen strikt voneinander trennt. In ABAP Cloud ermoeglicht CQRS hochoptimierte Systeme, bei denen Query- und Command-Pfade unabhaengig voneinander skaliert und optimiert werden koennen.
Was ist CQRS?
CQRS basiert auf dem Prinzip der Command Query Separation (CQS), erweitert dieses aber auf Architekturebene:
┌─────────────────────────────────────────────────────────────────────────────┐│ CQRS Architecture │├─────────────────────────────────────────────────────────────────────────────┤│ ││ ┌───────────────────┐ ││ │ Client │ ││ └─────────┬─────────┘ ││ │ ││ ┌─────────────┴─────────────┐ ││ │ │ ││ ▼ ▼ ││ ┌─────────────────┐ ┌─────────────────┐ ││ │ Command API │ │ Query API │ ││ │ (Schreiben) │ │ (Lesen) │ ││ └────────┬────────┘ └────────┬────────┘ ││ │ │ ││ ▼ ▼ ││ ┌─────────────────┐ ┌─────────────────┐ ││ │ Command Model │ │ Query Model │ ││ │ (Validierung, │ Event │ (Optimiert │ ││ │ Geschaefts- │ ──────> │ fuer Lesen, │ ││ │ logik) │ │ Denormalisiert│ ││ └────────┬────────┘ └────────┬────────┘ ││ │ │ ││ ▼ ▼ ││ ┌─────────────────┐ ┌─────────────────┐ ││ │ Write Store │ │ Read Store │ ││ │ (Normalisiert) │ │ (Denormalisiert)│ ││ └─────────────────┘ └─────────────────┘ │└─────────────────────────────────────────────────────────────────────────────┘Grundprinzipien
| Prinzip | Beschreibung |
|---|---|
| Trennung | Lese- und Schreibmodelle sind vollstaendig getrennt |
| Optimierung | Jedes Modell ist fuer seinen Zweck optimiert |
| Skalierbarkeit | Query- und Command-Seite unabhaengig skalierbar |
| Flexibilitaet | Unterschiedliche Speicher fuer Lesen und Schreiben moeglich |
Wann ist CQRS sinnvoll?
CQRS lohnt sich nicht fuer jedes Projekt. Hier sind Entscheidungskriterien:
CQRS empfohlen
| Szenario | Grund |
|---|---|
| Hohe Leserate (>90% Reads) | Query-Optimierung bringt groessten Nutzen |
| Komplexe Lesemodelle | Aggregierte, denormalisierte Views sinnvoll |
| Unterschiedliche Skalierung | Lese- und Schreiblast stark unterschiedlich |
| Event Sourcing geplant | CQRS ist natuerliche Ergaenzung |
| Komplexe Domaenenlogik | Command-Seite kann fokussiert bleiben |
CQRS nicht empfohlen
| Szenario | Grund |
|---|---|
| Einfache CRUD-Anwendungen | Overhead ueberwiegt Nutzen |
| Balancierte Lese-/Schreiblast | Keine eindeutige Optimierungsrichtung |
| Starke Konsistenzanforderung | Eventual Consistency problematisch |
| Kleines Team | Zusaetzliche Komplexitaet schwer wartbar |
CQRS in ABAP Cloud
In ABAP Cloud laesst sich CQRS elegant mit RAP umsetzen. Das Command Model entspricht dem transaktionalen RAP BO, waehrend das Query Model durch optimierte CDS Views repraesentiert wird.
Architekturueberblick
┌─────────────────────────────────────────────────────────────────────────────┐│ CQRS in ABAP Cloud │├─────────────────────────────────────────────────────────────────────────────┤│ ││ ┌─────────────────────────────────────────────────────────────────────┐ ││ │ Fiori App │ ││ └─────────────────────────────────────────────────────────────────────┘ ││ │ │ ││ │ Mutations │ Queries ││ ▼ ▼ ││ ┌─────────────────────────────┐ ┌──────────────────────────────────┐ ││ │ Transactional Service │ │ Analytical Service │ ││ │ (Command API) │ │ (Query API) │ ││ │ │ │ │ ││ │ ┌───────────────────────┐ │ │ ┌────────────────────────────┐ │ ││ │ │ ZC_Order (Projection) │ │ │ │ ZC_OrderAnalytics (Read) │ │ ││ │ │ - create, update │ │ │ │ - Aggregationen │ │ ││ │ │ - delete, actions │ │ │ │ - Joins, Berechnungen │ │ ││ │ └───────────────────────┘ │ │ └────────────────────────────┘ │ ││ │ │ │ │ │ │ ││ │ ▼ │ │ │ │ ││ │ ┌───────────────────────┐ │ │ │ │ ││ │ │ ZI_Order (Interface) │ │ │ │ │ ││ │ │ + Behavior Definition │ │ │ │ │ ││ │ └───────────────────────┘ │ │ │ │ ││ │ │ │ │ │ │ ││ └─────────────┼───────────────┘ └───────────────┼──────────────────┘ ││ │ │ ││ ▼ ▼ ││ ┌─────────────────────────────────────────────────────────────────────┐ ││ │ Database Tables │ ││ │ (Normalisiert, Single Source of Truth) │ ││ └─────────────────────────────────────────────────────────────────────┘ │└─────────────────────────────────────────────────────────────────────────────┘Query Model (Optimiert fuer Lesen)
Das Query Model in ABAP Cloud besteht aus CDS Views, die fuer schnelle Lesezugriffe optimiert sind.
Basis: Denormalisierte Lese-View
@EndUserText.label: 'Bestellungen - Query Model'@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
-- Denormalisierung: Kundendaten direkt eingebunden inner join zcustomer as Customer on Order.customer_id = Customer.customer_id
-- Denormalisierung: Materialstamm 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,
-- Kundendaten (denormalisiert) Customer.customer_id as CustomerId, Customer.customer_name as CustomerName, Customer.city as CustomerCity, Customer.country as CustomerCountry, Customer.credit_limit as CustomerCreditLimit,
-- Materialdaten (denormalisiert) Material.material_id as MaterialId, Material.material_text as MaterialText, Material.material_group as MaterialGroup,
-- Berechnete Felder Order.quantity as Quantity, Order.unit_price as UnitPrice, Order.quantity * Order.unit_price as TotalAmount,
-- Berechneter Status-Text case Order.status when 'N' then 'Neu' when 'P' then 'In Bearbeitung' when 'C' then 'Abgeschlossen' when 'X' then 'Storniert' else 'Unbekannt' end as StatusText,
-- Zeitliche Klassifizierung case when Order.order_date >= $session.system_date then 'Heute' when Order.order_date >= $session.system_date - 7 then 'Diese Woche' when Order.order_date >= $session.system_date - 30 then 'Dieser Monat' else 'Aelter' end as TimeCategory}Aggregierte Analyse-View
@EndUserText.label: 'Bestellungen - Aggregierte Analyse'@Analytics.dataCategory: #CUBEdefine view entity ZI_OrderAnalytics as select from ZI_OrderQuery{ -- Dimensionen @AnalyticsDetails.query.axis: #ROWS CustomerCountry,
@AnalyticsDetails.query.axis: #ROWS MaterialGroup,
@AnalyticsDetails.query.axis: #ROWS TimeCategory,
@AnalyticsDetails.query.axis: #ROWS Status,
-- Kennzahlen @Aggregation.default: #SUM TotalAmount,
@Aggregation.default: #SUM Quantity,
@Aggregation.default: #COUNT @EndUserText.label: 'Anzahl Bestellungen' cast(1 as abap.int4) as OrderCount,
@Aggregation.default: #AVG @EndUserText.label: 'Durchschnittlicher Bestellwert' TotalAmount as AvgOrderValue}Query Service Definition
@EndUserText.label: 'Order Query Service'@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);
-- Erweiterte Query-Felder CustomerRanking: abap.int4; DaysOpen : abap.int4;}Query Provider Implementation
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. " Paging und Sortierung aus Request 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( ).
" Optimierte Query zusammenbauen 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 'Neu' WHEN 'P' THEN 'In Bearbeitung' WHEN 'C' THEN 'Abgeschlossen' ELSE 'Unbekannt' END AS statustext, -- Ranking basierend auf Umsatz 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.
" Ergebnis zurueckgeben io_response->set_data( lt_result ). io_response->set_total_number_of_records( lines( lt_result ) ). ENDMETHOD.
ENDCLASS.Command Model (Optimiert fuer Schreiben)
Das Command Model fokussiert sich auf Geschaeftslogik, Validierungen und transaktionale Integritaet.
Interface View (Normalisiert)
@EndUserText.label: 'Bestellung - Command Model'@AccessControl.authorizationCheck: #CHECKdefine 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,
-- Assoziationen (keine Denormalisierung) _Customer, _Material, _Items}Behavior Definition (Commands)
managed implementation in class zbp_i_order unique;strict ( 2 );with draft;
define behavior for ZI_Order alias Orderpersistent table zorderdraft table zorder_dlock master total etag ChangedAtauthorization master ( instance )etag master ChangedAt{ // Standard-Commands create; update; delete;
// Feldsteuerung field ( readonly ) OrderId, CreatedBy, CreatedAt, ChangedBy, ChangedAt; field ( mandatory ) CustomerId, MaterialId, Quantity;
// Business Actions (Commands) action confirm result [1] $self; action cancel result [1] $self; action reopen result [1] $self;
// Factory Action factory action copyOrder [1];
// Validierungen 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 fuer Query-Synchronisation event orderCreated; event orderConfirmed; event orderCancelled; event orderChanged;
draft action Edit; draft action Activate; draft action Discard; draft action Resume; draft determine action Prepare;}Behavior Implementation
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. " Lese betroffene Bestellungen READ ENTITIES OF zi_order IN LOCAL MODE ENTITY Order FIELDS ( CustomerId ) WITH CORRESPONDING #( keys ) RESULT DATA(lt_orders).
" Pruefe Kunden 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 = |Kunde { ls_order-CustomerId } existiert nicht| ) ) 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 = |Kunde { ls_order-CustomerId } ist gesperrt| ) ) 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 = |Menge muss groesser als 0 sein| ) %element-Quantity = if_abap_behv=>mk-on ) TO reported-order. APPEND VALUE #( %tky = ls_order-%tky ) TO failed-order. ENDLOOP. ENDMETHOD.
METHOD confirm. " Lese Bestellungen 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). " Pruefe ob Bestaetigung moeglich IF ls_order-Status <> 'N'. APPEND VALUE #( %tky = ls_order-%tky %msg = new_message_with_text( severity = if_abap_behv_message=>severity-error text = |Nur neue Bestellungen koennen bestaetigt werden| ) ) TO reported-order. APPEND VALUE #( %tky = ls_order-%tky ) TO failed-order. CONTINUE. ENDIF.
" Status aendern MODIFY ENTITIES OF zi_order IN LOCAL MODE ENTITY Order UPDATE FIELDS ( Status ) WITH VALUE #( ( %tky = ls_order-%tky Status = 'C' " Confirmed ) ) REPORTED DATA(lt_update_reported) FAILED DATA(lt_update_failed).
" Event ausloesen fuer Query-Synchronisation RAISE ENTITY EVENT zi_order~orderConfirmed FROM VALUE #( ( OrderId = ls_order-OrderId ) ). ENDLOOP.
" Ergebnis zurueckgeben 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 = |Bestellung bereits storniert| ) ) 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).
" Hole Preise aus Materialstamm 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' " New ) ). ENDMETHOD.
ENDCLASS.Event-basierte Synchronisation
In einer vollstaendigen CQRS-Architektur werden Query- und Command-Modell durch Events synchronisiert.
Event Handler fuer Query-Aktualisierung
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 basierend auf Event-Typ 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. " Query-Cache aktualisieren oder Read-Store synchronisieren DATA(lt_event_data) = io_event->get_converted_event_data( ).
" Bei separatem Read-Store: Denormalisierte Daten schreiben " Bei Cache-Loesung: Cache invalidieren
LOOP AT lt_event_data INTO DATA(ls_event). " Hole vollstaendige Bestelldaten SELECT SINGLE * FROM zi_orderquery WHERE orderid = @ls_event-orderid INTO @DATA(ls_order_query).
" Schreibe in Read-Store (falls separate Tabelle) " MODIFY zorder_read FROM ls_order_query.
" Oder: Cache invalidieren " zcl_order_cache=>invalidate( ls_event-orderid ). ENDLOOP. ENDMETHOD.
METHOD handle_order_confirmed. " Status im Read-Store aktualisieren ENDMETHOD.
METHOD handle_order_cancelled. " Status im Read-Store aktualisieren ENDMETHOD.
METHOD handle_order_changed. " Geaenderte Felder im Read-Store aktualisieren ENDMETHOD.
ENDCLASS.Event Registration
CLASS zcl_order_event_registration DEFINITION PUBLIC FINAL.
PUBLIC SECTION. CLASS-METHODS class_constructor.
ENDCLASS.
CLASS zcl_order_event_registration IMPLEMENTATION.
METHOD class_constructor. " Registriere Event Handler cl_abap_behv_event_registration=>register_event_handler( handler_class = 'ZCL_ORDER_EVENT_HANDLER' entity = 'ZI_ORDER' ). ENDMETHOD.
ENDCLASS.Integration mit RAP
Die CQRS-Trennung in RAP erfolgt durch separate Services fuer Lesen und Schreiben.
Transactional Service (Commands)
@EndUserText.label: 'Order Management Service'define service ZUI_ORDER_O4 { expose ZC_Order as Order; expose ZC_OrderItem as OrderItem;}Analytics Service (Queries)
@EndUserText.label: 'Order Analytics Service'define service ZUI_ORDER_ANALYTICS_O4 { expose ZC_OrderAnalytics as OrderAnalytics; expose ZC_OrderQuery as OrderQuery;}Service Binding Trennung
In den Service Bindings wird explizit der Zweck definiert:
- Transactional Service: OData V4 mit vollstaendigem CRUD
- Analytics Service: OData V4 Analytics (nur Lesezugriff)
Best Practices
1. Klare Verantwortlichkeiten
" Command Handler - fokussiert auf GeschaeftslogikCLASS 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 - fokussiert auf DatenabrufCLASS 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. Eventual Consistency beruecksichtigen
" Query mit Konsistenz-HinweisMETHOD get_order_status. " Hinweis: Daten koennen bis zu X Sekunden verzoegert sein SELECT SINGLE status, last_sync_timestamp FROM zorder_read WHERE order_id = @iv_order_id INTO @DATA(ls_result).
" Bei kritischen Abfragen: Direkt aus Command-Store lesen 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. Separate Datenmodelle
| Aspekt | Command Model | Query Model |
|---|---|---|
| Normalisierung | 3NF, normalisiert | Denormalisiert |
| Optimierung | Schreib-Performance | Lese-Performance |
| Indizes | Write-optimiert | Read-optimiert |
| Validierung | Vollstaendig | Keine |
| Geschaeftslogik | Ja | Nein |
4. Idempotente Commands
METHOD execute_command. " Pruefe ob Command bereits ausgefuehrt wurde SELECT SINGLE @abap_true FROM zcommand_log WHERE command_id = @is_command-command_id INTO @DATA(lv_exists).
IF lv_exists = abap_true. " Idempotent: Bereits ausgefuehrt, kein Fehler RETURN. ENDIF.
" Command ausfuehren " ...
" Loggen INSERT zcommand_log FROM @( VALUE #( command_id = is_command-command_id executed_at = utclong_current( ) executed_by = sy-uname ) ).ENDMETHOD.Zusammenfassung
| Aspekt | Beschreibung |
|---|---|
| Query Model | CDS Views, denormalisiert, lese-optimiert |
| Command Model | RAP BO mit Behavior Definition, normalisiert |
| Synchronisation | RAP Business Events oder Background Jobs |
| Konsistenz | Eventual Consistency akzeptieren |
| Skalierung | Getrennte Services fuer Lesen/Schreiben |
CQRS in ABAP Cloud ermoeglicht hochoptimierte Systeme fuer komplexe Anforderungen. Die Trennung von Lese- und Schreibmodell bietet Flexibilitaet bei der Optimierung und Skalierung. Allerdings erfordert das Pattern zusaetzliche Komplexitaet und sollte nur bei klarem Nutzen eingesetzt werden.
Verwandte Themen
- Design Patterns fuer RAP - Weitere Architekturmuster
- Event-Driven Architecture - Asynchrone Kommunikation
- RAP Grundlagen - RESTful ABAP Programming
- Custom Analytical Queries - Analytische CDS Views
- RAP Custom Entities - Flexible Query Provider