Joule ist SAPs generative KI, die seit 2024 direkt in ADT (ABAP Development Tools) integriert ist. Als nativer SAP-Assistent versteht Joule ABAP-Syntax, RAP-Patterns und SAP-spezifische Konzepte und kann ABAP-Entwickler bei der täglichen Arbeit unterstützen.
Was ist Joule?
Joule = SAPs generativer KI-Assistent
- Basiert auf Large Language Models, trainiert auf SAP-Dokumentation und ABAP-Code
- Nativ in SAP-Tools integriert (ADT, BAS, SAP Build)
- Versteht SAP-spezifische Konzepte: RAP, CDS, Fiori, OData
- Kann Code generieren, erklären und dokumentieren
- Generiert Unit Tests und Testdaten
- Berücksichtigt ABAP Cloud Regeln und Clean ABAP Guidelines
Joule vs. GitHub Copilot
| Aspekt | Joule | GitHub Copilot |
|---|---|---|
| Hersteller | SAP | GitHub/Microsoft |
| Integration | ADT, BAS, SAP Build | VS Code, Eclipse |
| SAP-Wissen | Nativ (SAP-Dokumentation) | Generisch (Open Source) |
| RAP-Support | Sehr gut (Behavior, CDS, Service) | Gut (aus Code-Beispielen) |
| ABAP Cloud | Optimiert für Released APIs | Gemischt (auch Classic ABAP) |
| Clean ABAP | Integriert | Muss trainiert werden |
| Lizenz | SAP-Lizenz erforderlich | $10-19/Monat |
| Offline | Nein (Cloud-basiert) | Nein (Cloud-basiert) |
| Datenschutz | SAP-Cloud (EU-Server möglich) | Microsoft-Cloud |
Empfehlung:
- Joule für SAP-zentrierte Entwicklung und RAP-Projekte
- Copilot für gemischte Umgebungen (ABAP + andere Sprachen)
- Beide zusammen für maximale Produktivität
Joule in ADT aktivieren
Voraussetzungen
- SAP BTP oder S/4HANA Cloud mit Joule-Berechtigung
- ADT (Eclipse) Version 2024 oder neuer
- Joule-Lizenz im SAP-Vertrag enthalten
- Internet-Verbindung (Cloud-basiert)
Aktivierung in ADT
1. ADT öffnen → Window → Preferences2. ABAP Development → Joule3. "Enable Joule AI Assistant" aktivieren4. System-Verbindung auswählen (BTP oder S/4HANA Cloud)5. Authentifizierung mit SAP-Benutzer6. OK → ADT neu startenJoule-Panel öffnen:
View → Other → ABAP → Joule AssistantOder: Ctrl+Shift+J (Shortcut)Code-Generierung mit Joule
Beispiel 1: RAP Business Object erstellen
Prompt an Joule:
Erstelle ein RAP Business Object für Bestellungen (Orders) mit:- Felder: OrderId, CustomerId, OrderDate, TotalAmount, Status- Managed Szenario mit Draft- Validierung: OrderDate darf nicht in der Vergangenheit liegen- Determination: TotalAmount berechnen aus OrderItems- Action: approve zum Genehmigen der BestellungJoule generiert:
- Datenbanktabelle:
@EndUserText.label : 'Order Header'@AbapCatalog.enhancement.category : #NOT_EXTENSIBLE@AbapCatalog.tableCategory : #TRANSPARENT@AbapCatalog.deliveryClass : #Adefine table zorder_hdr { key client : abap.clnt not null; key order_id : abap.numc(10) not null; customer_id : abap.numc(10); order_date : abap.dats; total_amount : abap.dec(15,2); currency : abap.cuky(5); status : abap.char(1); created_by : abp_creation_user; created_at : abp_creation_tstmpl; last_changed_by : abp_lastchange_user; last_changed_at : abp_lastchange_tstmpl; local_changed_at: abp_locinst_lastchange_tstmpl;}- CDS Interface View:
@AccessControl.authorizationCheck: #CHECK@EndUserText.label: 'Order Interface View'
define root view entity ZI_Order as select from zorder_hdr composition [0..*] of ZI_OrderItem as _Items association [0..1] to I_Customer as _Customer on $projection.CustomerId = _Customer.Customer{ key order_id as OrderId, customer_id as CustomerId, order_date as OrderDate,
@Semantics.amount.currencyCode: 'Currency' total_amount as TotalAmount,
@Semantics.currencyCode: true currency as Currency,
status as Status,
@Semantics.user.createdBy: true created_by as CreatedBy,
@Semantics.systemDateTime.createdAt: true created_at as CreatedAt,
@Semantics.user.lastChangedBy: true last_changed_by as LastChangedBy,
@Semantics.systemDateTime.lastChangedAt: true last_changed_at as LastChangedAt,
@Semantics.systemDateTime.localInstanceLastChangedAt: true local_changed_at as LocalChangedAt,
_Items, _Customer}- Behavior Definition:
managed implementation in class zbp_i_order unique;strict ( 2 );with draft;
define behavior for ZI_Order alias Orderpersistent table zorder_hdrdraft table zorder_hdr_dlock master total etag LastChangedAtauthorization master ( global )etag master LocalChangedAt{ field ( readonly ) OrderId, CreatedBy, CreatedAt, LastChangedBy, LastChangedAt; field ( readonly : update ) CustomerId;
create; update; delete;
// Draft Actions draft action Edit; draft action Activate optimized; draft action Discard; draft action Resume; draft determine action Prepare;
// Custom Action action approve result [1] $self;
// Validation validation validateOrderDate on save { create; update; field OrderDate; }
// Determination determination calculateTotalAmount on modify { field TotalAmount; }
association _Items { create; with draft; }
mapping for zorder_hdr { OrderId = order_id; CustomerId = customer_id; OrderDate = order_date; TotalAmount = total_amount; Currency = currency; Status = status; CreatedBy = created_by; CreatedAt = created_at; LastChangedBy = last_changed_by; LastChangedAt = last_changed_at; LocalChangedAt = local_changed_at; }}- Behavior Implementation:
CLASS zbp_i_order DEFINITION PUBLIC ABSTRACT FINAL FOR BEHAVIOR OF zi_order.ENDCLASS.
CLASS zbp_i_order IMPLEMENTATION.ENDCLASS.
CLASS lhc_order DEFINITION INHERITING FROM cl_abap_behavior_handler. PRIVATE SECTION. METHODS: get_global_authorizations FOR GLOBAL AUTHORIZATION IMPORTING REQUEST requested_authorizations FOR Order RESULT result,
validateOrderDate FOR VALIDATE ON SAVE IMPORTING keys FOR Order~validateOrderDate,
calculateTotalAmount FOR DETERMINE ON MODIFY IMPORTING keys FOR Order~calculateTotalAmount,
approve FOR MODIFY IMPORTING keys FOR ACTION Order~approve RESULT result.ENDCLASS.
CLASS lhc_order IMPLEMENTATION.
METHOD get_global_authorizations. " Simplified: Allow all operations result = VALUE #( ( %action-approve = if_abap_behv=>auth-allowed ) ). ENDMETHOD.
METHOD validateOrderDate. " Read order data READ ENTITIES OF zi_order IN LOCAL MODE ENTITY Order FIELDS ( OrderDate ) WITH CORRESPONDING #( keys ) RESULT DATA(lt_orders).
" Get current date DATA(lv_today) = cl_abap_context_info=>get_system_date( ).
" Check each order LOOP AT lt_orders INTO DATA(ls_order). IF ls_order-OrderDate < lv_today. " Order date is in the past → Error APPEND VALUE #( %tky = ls_order-%tky %element-OrderDate = if_abap_behv=>mk-on ) TO failed-order.
APPEND VALUE #( %tky = ls_order-%tky %element-OrderDate = if_abap_behv=>mk-on %msg = new_message_with_text( severity = if_abap_behv_message=>severity-error text = |Bestelldatum { ls_order-OrderDate DATE = USER } liegt in der Vergangenheit| ) ) TO reported-order. ENDIF. ENDLOOP. ENDMETHOD.
METHOD calculateTotalAmount. " Read orders with items READ ENTITIES OF zi_order IN LOCAL MODE ENTITY Order BY \_Items ALL FIELDS WITH CORRESPONDING #( keys ) RESULT DATA(lt_items).
" Calculate total per order LOOP AT keys INTO DATA(ls_key). DATA(lv_total) = REDUCE decfloat34( INIT sum = CONV decfloat34( 0 ) FOR item IN lt_items WHERE ( OrderId = ls_key-OrderId ) NEXT sum = sum + ( item-Quantity * item-UnitPrice ) ).
" Update order total MODIFY ENTITIES OF zi_order IN LOCAL MODE ENTITY Order UPDATE FIELDS ( TotalAmount ) WITH VALUE #( ( %tky = ls_key-%tky TotalAmount = lv_total ) ) FAILED DATA(lt_failed) REPORTED DATA(lt_reported). ENDLOOP. ENDMETHOD.
METHOD approve. " Read current status READ ENTITIES OF zi_order IN LOCAL MODE ENTITY Order FIELDS ( Status ) WITH CORRESPONDING #( keys ) RESULT DATA(lt_orders).
" Update status to 'A' (Approved) MODIFY ENTITIES OF zi_order IN LOCAL MODE ENTITY Order UPDATE FIELDS ( Status ) WITH VALUE #( FOR order IN lt_orders ( %tky = order-%tky Status = 'A' ) ) FAILED failed REPORTED reported.
" Return updated orders 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.
ENDCLASS.Beispiel 2: CDS View mit Aggregationen
Prompt an Joule:
Erstelle einen CDS View für Verkaufsstatistiken:- Gruppiert nach Monat und Verkäufer- Zeigt Anzahl Bestellungen, Gesamtumsatz, Durchschnittswert- Filter auf Status = 'Completed'- Sortiert nach Umsatz absteigendJoule generiert:
@AccessControl.authorizationCheck: #CHECK@EndUserText.label: 'Sales Statistics by Month and Salesperson'@Analytics.dataCategory: #CUBE@ObjectModel.usageType: { serviceQuality: #C, sizeCategory: #L, dataClass: #MIXED}
define view entity ZI_SalesStatistics as select from ZI_Order as Order association [0..1] to I_CalendarMonth as _CalendarMonth on $projection.OrderMonth = _CalendarMonth.CalendarMonth{ // Dimensions @EndUserText.label: 'Bestellmonat' concat( cast( substring( cast( Order.OrderDate as abap.char(8) ), 1, 6 ) as abap.char(6) ), '' ) as OrderMonth,
@EndUserText.label: 'Verkäufer' Order.CreatedBy as Salesperson,
@EndUserText.label: 'Währung' @Semantics.currencyCode: true Order.Currency,
// Measures @EndUserText.label: 'Anzahl Bestellungen' @DefaultAggregation: #SUM @Aggregation.default: #SUM cast( 1 as abap.int4 ) as OrderCount,
@EndUserText.label: 'Gesamtumsatz' @Semantics.amount.currencyCode: 'Currency' @DefaultAggregation: #SUM @Aggregation.default: #SUM Order.TotalAmount as TotalRevenue,
// Association _CalendarMonth}where Order.Status = 'C' // CompletedConsumption View mit Aggregationen:
@AccessControl.authorizationCheck: #CHECK@EndUserText.label: 'Sales Report'@Metadata.allowExtensions: true
@UI: { headerInfo: { typeName: 'Verkaufsstatistik', typeNamePlural: 'Verkaufsstatistiken' }, presentationVariant: [{ sortOrder: [{ by: 'TotalRevenue', direction: #DESC }] }]}
define view entity ZC_SalesStatistics as select from ZI_SalesStatistics{ @UI.lineItem: [{ position: 10 }] @UI.selectionField: [{ position: 10 }] key OrderMonth,
@UI.lineItem: [{ position: 20 }] @UI.selectionField: [{ position: 20 }] key Salesperson,
Currency,
@UI.lineItem: [{ position: 30 }] @EndUserText.label: 'Anzahl Bestellungen' sum( OrderCount ) as OrderCount,
@UI.lineItem: [{ position: 40 }] @Semantics.amount.currencyCode: 'Currency' @EndUserText.label: 'Gesamtumsatz' sum( TotalRevenue ) as TotalRevenue,
@UI.lineItem: [{ position: 50 }] @Semantics.amount.currencyCode: 'Currency' @EndUserText.label: 'Durchschnittswert' division( sum( TotalRevenue ), sum( OrderCount ), 2 ) as AverageOrderValue,
_CalendarMonth}group by OrderMonth, Salesperson, CurrencyCode-Erklärung und Dokumentation
Code erklären lassen
Markieren Sie Code und fragen Sie Joule:
Erkläre diesen ABAP-Code:
DATA(lt_filtered) = FILTER #( lt_orders USING KEY status_key WHERE status = 'A' AND customer_id IS NOT INITIAL).Joule antwortet:
Diese Zeile verwendet die ABAP FILTER-Expression mit optimierterSuche über einen Secondary Key:
1. **FILTER #( lt_orders ... )**: Filtert die interne Tabelle lt_orders
2. **USING KEY status_key**: Nutzt den benannten Secondary Key 'status_key' für die Suche. Dies ist deutlich performanter als ein vollständiger Tabellenscan, da der Key als sortierter oder Hash-Index genutzt wird.
3. **WHERE status = 'A' AND customer_id IS NOT INITIAL**: Filterbedingungen: - status muss 'A' (Approved) sein - customer_id darf nicht initial (leer) sein
4. **DATA(lt_filtered) = ...**: Inline-Deklaration - der Typ wird automatisch vom Ergebnis abgeleitet.
**Performance-Hinweis:**Der Secondary Key muss zur Tabellendefinition passen:- Für WHERE mit = → HASHED KEY verwenden- Für WHERE mit Bereich → SORTED KEY verwenden
**Äquivalent (langsamer):**LOOP AT lt_orders INTO DATA(ls_order) WHERE status = 'A' AND customer_id IS NOT INITIAL. APPEND ls_order TO lt_filtered.ENDLOOP.Dokumentation generieren
Prompt:
Generiere ABAP Doc für diese Methode:
METHOD calculate_discount. DATA(lv_base) = iv_amount. IF iv_customer_type = 'VIP'. rv_discount = lv_base * '0.15'. ELSEIF iv_amount > 1000. rv_discount = lv_base * '0.10'. ELSEIF iv_amount > 500. rv_discount = lv_base * '0.05'. ELSE. rv_discount = 0. ENDIF.ENDMETHOD.Joule generiert:
"! <p class="shorttext synchronized">Berechnet den Rabattbetrag</p>"! Ermittelt den Rabatt basierend auf Kundentyp und Bestellwert."! VIP-Kunden erhalten immer 15% Rabatt. Für andere Kunden gilt:"! <ul>"! <li>Bestellung > 1000: 10% Rabatt</li>"! <li>Bestellung > 500: 5% Rabatt</li>"! <li>Sonst: kein Rabatt</li>"! </ul>"!"! @parameter iv_amount | <p class="shorttext synchronized">Bestellbetrag</p>"! Der Brutto-Bestellwert als Berechnungsgrundlage"! @parameter iv_customer_type | <p class="shorttext synchronized">Kundentyp</p>"! 'VIP' für VIP-Kunden, sonst Standard"! @parameter rv_discount | <p class="shorttext synchronized">Rabattbetrag</p>"! Der berechnete Rabatt in der gleichen Währung"! @raising cx_parameter_invalid | Bei negativem BestellbetragMETHOD calculate_discount. IF iv_amount < 0. RAISE EXCEPTION TYPE cx_parameter_invalid EXPORTING parameter_name = 'IV_AMOUNT'. ENDIF.
IF iv_customer_type = 'VIP'. rv_discount = iv_amount * '0.15'. ELSEIF iv_amount > 1000. rv_discount = iv_amount * '0.10'. ELSEIF iv_amount > 500. rv_discount = iv_amount * '0.05'. ELSE. rv_discount = 0. ENDIF.ENDMETHOD.Unit Test Generierung
Prompt für Unit Tests
Generiere Unit Tests für die Methode calculate_discount:- Test VIP mit 100€ → 15€ Rabatt- Test Normal mit 1500€ → 150€ Rabatt (10%)- Test Normal mit 750€ → 37.50€ Rabatt (5%)- Test Normal mit 100€ → 0€ Rabatt- Test negativer Betrag → ExceptionJoule generiert:
"! @testing ZCL_DISCOUNT_CALCULATORCLASS ltc_discount_calculator DEFINITION FINAL FOR TESTING DURATION SHORT RISK LEVEL HARMLESS.
PRIVATE SECTION. DATA mo_cut TYPE REF TO zcl_discount_calculator.
METHODS: setup,
"! VIP-Kunden erhalten immer 15% Rabatt test_vip_customer_15_percent FOR TESTING,
"! Bestellung > 1000 erhält 10% Rabatt test_large_order_10_percent FOR TESTING,
"! Bestellung > 500 erhält 5% Rabatt test_medium_order_5_percent FOR TESTING,
"! Bestellung <= 500 ohne VIP erhält keinen Rabatt test_small_order_no_discount FOR TESTING,
"! Negativer Betrag wirft Exception test_negative_amount_exception FOR TESTING.ENDCLASS.
CLASS ltc_discount_calculator IMPLEMENTATION.
METHOD setup. mo_cut = NEW zcl_discount_calculator( ). ENDMETHOD.
METHOD test_vip_customer_15_percent. " Arrange DATA(lv_amount) = CONV decfloat34( 100 ). DATA(lv_customer_type) = 'VIP'.
" Act DATA(lv_discount) = mo_cut->calculate_discount( iv_amount = lv_amount iv_customer_type = lv_customer_type ).
" Assert cl_abap_unit_assert=>assert_equals( act = lv_discount exp = CONV decfloat34( 15 ) msg = 'VIP mit 100€ sollte 15€ Rabatt erhalten (15%)' ). ENDMETHOD.
METHOD test_large_order_10_percent. " Arrange DATA(lv_amount) = CONV decfloat34( 1500 ). DATA(lv_customer_type) = 'STD'.
" Act DATA(lv_discount) = mo_cut->calculate_discount( iv_amount = lv_amount iv_customer_type = lv_customer_type ).
" Assert cl_abap_unit_assert=>assert_equals( act = lv_discount exp = CONV decfloat34( 150 ) msg = 'Standard mit 1500€ sollte 150€ Rabatt erhalten (10%)' ). ENDMETHOD.
METHOD test_medium_order_5_percent. " Arrange DATA(lv_amount) = CONV decfloat34( 750 ). DATA(lv_customer_type) = 'STD'.
" Act DATA(lv_discount) = mo_cut->calculate_discount( iv_amount = lv_amount iv_customer_type = lv_customer_type ).
" Assert cl_abap_unit_assert=>assert_equals( act = lv_discount exp = CONV decfloat34( '37.5' ) msg = 'Standard mit 750€ sollte 37.50€ Rabatt erhalten (5%)' ). ENDMETHOD.
METHOD test_small_order_no_discount. " Arrange DATA(lv_amount) = CONV decfloat34( 100 ). DATA(lv_customer_type) = 'STD'.
" Act DATA(lv_discount) = mo_cut->calculate_discount( iv_amount = lv_amount iv_customer_type = lv_customer_type ).
" Assert cl_abap_unit_assert=>assert_equals( act = lv_discount exp = CONV decfloat34( 0 ) msg = 'Standard mit 100€ sollte keinen Rabatt erhalten' ). ENDMETHOD.
METHOD test_negative_amount_exception. " Arrange DATA(lv_amount) = CONV decfloat34( -100 ).
" Act & Assert TRY. mo_cut->calculate_discount( iv_amount = lv_amount iv_customer_type = 'STD' ). cl_abap_unit_assert=>fail( msg = 'Exception erwartet für negativen Betrag' ). CATCH cx_parameter_invalid INTO DATA(lx_error). " Expected exception cl_abap_unit_assert=>assert_bound( act = lx_error msg = 'Exception sollte geworfen werden' ). ENDTRY. ENDMETHOD.
ENDCLASS.Praktisches Beispiel: RAP Service komplett erstellen
Aufgabe
Erstellen Sie einen vollständigen RAP-Service für eine Aufgabenverwaltung (Task Management) mit Joule.
Schritt 1: Anforderungen an Joule
Erstelle einen RAP Business Object Service für Aufgabenverwaltung:
Entität: Task- TaskId (Key, automatisch generiert)- Title (Pflichtfeld, max 100 Zeichen)- Description (optional, 1000 Zeichen)- Priority (High, Medium, Low)- Status (Open, InProgress, Completed, Cancelled)- DueDate (optional)- AssignedTo (Benutzer)- CreatedAt, CreatedBy, ChangedAt, ChangedBy
Anforderungen:1. Managed RAP mit Draft2. Validierung: Title ist Pflicht3. Validierung: DueDate >= heute (wenn gesetzt)4. Determination: Status auf 'Open' bei Create5. Action: complete (setzt Status auf 'Completed')6. Action: cancel (setzt Status auf 'Cancelled')7. Feature Control: complete nur wenn Status = 'Open' oder 'InProgress'8. UI Annotations für Fiori Elements List ReportSchritt 2: Joule generiert alle Objekte
Joule erstellt automatisch:
- Datenbanktabelle
ztask - Draft-Tabelle
ztask_d - Interface View
ZI_Task - Consumption View
ZC_Task - Metadata Extension für UI
- Behavior Definition
- Behavior Implementation mit allen Methoden
- Service Definition
- Service Binding
Generierte Behavior Implementation (Auszug):
CLASS lhc_task DEFINITION INHERITING FROM cl_abap_behavior_handler. PRIVATE SECTION. METHODS: get_instance_features FOR INSTANCE FEATURES IMPORTING keys REQUEST requested_features FOR Task RESULT result,
validateTitle FOR VALIDATE ON SAVE IMPORTING keys FOR Task~validateTitle,
validateDueDate FOR VALIDATE ON SAVE IMPORTING keys FOR Task~validateDueDate,
setInitialStatus FOR DETERMINE ON MODIFY IMPORTING keys FOR Task~setInitialStatus,
complete FOR MODIFY IMPORTING keys FOR ACTION Task~complete RESULT result,
cancel FOR MODIFY IMPORTING keys FOR ACTION Task~cancel RESULT result.ENDCLASS.
CLASS lhc_task IMPLEMENTATION.
METHOD get_instance_features. " Read current status READ ENTITIES OF zi_task IN LOCAL MODE ENTITY Task FIELDS ( Status ) WITH CORRESPONDING #( keys ) RESULT DATA(lt_tasks) FAILED failed.
" Set feature control based on status result = VALUE #( FOR task IN lt_tasks ( %tky = task-%tky " complete nur wenn Open oder InProgress %action-complete = COND #( WHEN task-Status = 'O' OR task-Status = 'I' THEN if_abap_behv=>fc-o-enabled ELSE if_abap_behv=>fc-o-disabled ) " cancel nicht wenn bereits Completed oder Cancelled %action-cancel = COND #( WHEN task-Status = 'C' OR task-Status = 'X' THEN if_abap_behv=>fc-o-disabled ELSE if_abap_behv=>fc-o-enabled ) ) ). ENDMETHOD.
METHOD validateTitle. READ ENTITIES OF zi_task IN LOCAL MODE ENTITY Task FIELDS ( Title ) WITH CORRESPONDING #( keys ) RESULT DATA(lt_tasks).
LOOP AT lt_tasks INTO DATA(ls_task). IF ls_task-Title IS INITIAL. APPEND VALUE #( %tky = ls_task-%tky ) TO failed-task. APPEND VALUE #( %tky = ls_task-%tky %element-Title = if_abap_behv=>mk-on %msg = new_message_with_text( severity = if_abap_behv_message=>severity-error text = 'Titel ist ein Pflichtfeld' ) ) TO reported-task. ENDIF. ENDLOOP. ENDMETHOD.
METHOD validateDueDate. READ ENTITIES OF zi_task IN LOCAL MODE ENTITY Task FIELDS ( DueDate ) WITH CORRESPONDING #( keys ) RESULT DATA(lt_tasks).
DATA(lv_today) = cl_abap_context_info=>get_system_date( ).
LOOP AT lt_tasks INTO DATA(ls_task) WHERE DueDate IS NOT INITIAL AND DueDate < lv_today. APPEND VALUE #( %tky = ls_task-%tky ) TO failed-task. APPEND VALUE #( %tky = ls_task-%tky %element-DueDate = if_abap_behv=>mk-on %msg = new_message_with_text( severity = if_abap_behv_message=>severity-error text = 'Fälligkeitsdatum darf nicht in der Vergangenheit liegen' ) ) TO reported-task. ENDLOOP. ENDMETHOD.
METHOD setInitialStatus. " Set status to 'Open' for new tasks MODIFY ENTITIES OF zi_task IN LOCAL MODE ENTITY Task UPDATE FIELDS ( Status ) WITH VALUE #( FOR key IN keys ( %tky = key-%tky Status = 'O' " Open ) ) REPORTED reported. ENDMETHOD.
METHOD complete. " Update status to Completed MODIFY ENTITIES OF zi_task IN LOCAL MODE ENTITY Task UPDATE FIELDS ( Status ) WITH VALUE #( FOR key IN keys ( %tky = key-%tky Status = 'C' " Completed ) ) FAILED failed REPORTED reported.
" Return result READ ENTITIES OF zi_task IN LOCAL MODE ENTITY Task ALL FIELDS WITH CORRESPONDING #( keys ) RESULT DATA(lt_tasks).
result = VALUE #( FOR task IN lt_tasks ( %tky = task-%tky %param = task ) ). ENDMETHOD.
METHOD cancel. " Update status to Cancelled MODIFY ENTITIES OF zi_task IN LOCAL MODE ENTITY Task UPDATE FIELDS ( Status ) WITH VALUE #( FOR key IN keys ( %tky = key-%tky Status = 'X' " Cancelled ) ) FAILED failed REPORTED reported.
" Return result READ ENTITIES OF zi_task IN LOCAL MODE ENTITY Task ALL FIELDS WITH CORRESPONDING #( keys ) RESULT DATA(lt_tasks).
result = VALUE #( FOR task IN lt_tasks ( %tky = task-%tky %param = task ) ). ENDMETHOD.
ENDCLASS.Limitierungen und Best Practices
Limitierungen von Joule
| Limitation | Beschreibung | Workaround |
|---|---|---|
| Firmenlogik | Kennt keine internen Geschäftsregeln | Spezifische Prompts mit Kontext |
| Z-Tabellen | Kennt Custom-Strukturen nicht | Struktur im Prompt mitgeben |
| Komplexe Architektur | Kann keine Gesamtarchitektur planen | Aufgaben in kleine Teile splitten |
| 100% Korrektheit | Generierter Code kann Fehler enthalten | Immer testen und reviewen |
| Performance | Optimiert nicht für spezifische Datenmengen | Performance-Tests durchführen |
| Security | Kann Sicherheitslücken einführen | Security-Review durchführen |
| Offline | Benötigt Internet-Verbindung | Alternative: lokale Snippets |
Best Practices für Joule
" ✅ DO: Spezifische Prompts mit Kontext" Prompt: "Erstelle Validation für Travel.BeginDate:" - Muss >= heute sein" - Muss <= EndDate sein" - Message Class: ZTRAVEL, Nummer 001 und 002"
" ✅ DO: Iterativ arbeiten" 1. Groben Code generieren lassen" 2. Reviewen und anpassen" 3. Spezifische Teile verfeinern lassen
" ✅ DO: Code immer testen" - Unit Tests generieren lassen" - Manuell durchspielen" - Edge Cases prüfen
" ✅ DO: Clean ABAP befolgen" Joule kennt Clean ABAP Guidelines" Prompt: "Refactor to follow Clean ABAP naming conventions"
" ❌ DON'T: Blind übernehmen" - Jeden generierten Code reviewen" - Logik verstehen" - An Firmenvorgaben anpassen
" ❌ DON'T: Zu viel auf einmal" Schlecht: "Erstelle komplette ERP-Anwendung"" Gut: "Erstelle CDS View für Kundenübersicht"
" ❌ DON'T: Security ignorieren" - Berechtigungsprüfungen hinzufügen" - SQL Injection prüfen" - Keine Secrets im CodeJoule-Workflow für ABAP-Entwicklung
┌──────────────────────────────────────────────────┐│ 1. ANFORDERUNG ANALYSIEREN ││ - Was soll das Objekt tun? ││ - Welche Felder, Aktionen, Validierungen? │└────────────────────────┬─────────────────────────┘ │ ▼┌──────────────────────────────────────────────────┐│ 2. PROMPT FORMULIEREN ││ - Spezifisch und detailliert ││ - Kontext mitgeben (Tabellenstrukturen, etc.) ││ - Constraints nennen (Clean ABAP, Released APIs)│└────────────────────────┬─────────────────────────┘ │ ▼┌──────────────────────────────────────────────────┐│ 3. JOULE GENERIERT ││ - Code reviewen ││ - Syntax prüfen ││ - An Firmenvorgaben anpassen │└────────────────────────┬─────────────────────────┘ │ ▼┌──────────────────────────────────────────────────┐│ 4. TESTEN ││ - Unit Tests (auch von Joule generiert) ││ - Integration Tests ││ - Edge Cases │└────────────────────────┬─────────────────────────┘ │ ▼┌──────────────────────────────────────────────────┐│ 5. VERFEINERN ││ - Feedback an Joule: "Optimiere für Performance"││ - Iterativ verbessern ││ - Dokumentation generieren lassen │└──────────────────────────────────────────────────┘Fazit
Joule ist ein leistungsstarker KI-Assistent für ABAP Cloud Entwicklung mit nativer SAP-Integration. Im Vergleich zu GitHub Copilot bietet Joule besseres Verständnis für SAP-spezifische Konzepte wie RAP, CDS Views und Fiori. Die Kombination aus Code-Generierung, Erklärung und Test-Generierung kann die Produktivität deutlich steigern.
Wichtig: Joule ist ein Assistent, kein Ersatz für Entwickler-Know-how. Generierter Code muss immer geprüft, getestet und an Firmenstandards angepasst werden.
Weitere Ressourcen
- GitHub Copilot für ABAP - Alternative KI für ABAP-Entwicklung
- RAP Basics - Grundlagen des ABAP RESTful Application Programming Model
- Clean ABAP - Guidelines für wartbaren ABAP-Code
- ABAP Cloud Definition - Was ist ABAP Cloud?