Joule für ABAP: SAPs generative KI für ABAP Cloud Entwicklung

kategorie
Trends
Veröffentlicht
autor
Johannes

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

AspektJouleGitHub Copilot
HerstellerSAPGitHub/Microsoft
IntegrationADT, BAS, SAP BuildVS Code, Eclipse
SAP-WissenNativ (SAP-Dokumentation)Generisch (Open Source)
RAP-SupportSehr gut (Behavior, CDS, Service)Gut (aus Code-Beispielen)
ABAP CloudOptimiert für Released APIsGemischt (auch Classic ABAP)
Clean ABAPIntegriertMuss trainiert werden
LizenzSAP-Lizenz erforderlich$10-19/Monat
OfflineNein (Cloud-basiert)Nein (Cloud-basiert)
DatenschutzSAP-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 → Preferences
2. ABAP Development → Joule
3. "Enable Joule AI Assistant" aktivieren
4. System-Verbindung auswählen (BTP oder S/4HANA Cloud)
5. Authentifizierung mit SAP-Benutzer
6. OK → ADT neu starten

Joule-Panel öffnen:

View → Other → ABAP → Joule Assistant
Oder: 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 Bestellung

Joule generiert:

  1. Datenbanktabelle:
@EndUserText.label : 'Order Header'
@AbapCatalog.enhancement.category : #NOT_EXTENSIBLE
@AbapCatalog.tableCategory : #TRANSPARENT
@AbapCatalog.deliveryClass : #A
define 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;
}
  1. 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
}
  1. Behavior Definition:
managed implementation in class zbp_i_order unique;
strict ( 2 );
with draft;
define behavior for ZI_Order alias Order
persistent table zorder_hdr
draft table zorder_hdr_d
lock master total etag LastChangedAt
authorization 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;
}
}
  1. 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 absteigend

Joule 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' // Completed

Consumption 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,
Currency

Code-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 optimierter
Suche ü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 Bestellbetrag
METHOD 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 → Exception

Joule generiert:

"! @testing ZCL_DISCOUNT_CALCULATOR
CLASS 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 Draft
2. Validierung: Title ist Pflicht
3. Validierung: DueDate >= heute (wenn gesetzt)
4. Determination: Status auf 'Open' bei Create
5. 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 Report

Schritt 2: Joule generiert alle Objekte

Joule erstellt automatisch:

  1. Datenbanktabelle ztask
  2. Draft-Tabelle ztask_d
  3. Interface View ZI_Task
  4. Consumption View ZC_Task
  5. Metadata Extension für UI
  6. Behavior Definition
  7. Behavior Implementation mit allen Methoden
  8. Service Definition
  9. 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

LimitationBeschreibungWorkaround
FirmenlogikKennt keine internen GeschäftsregelnSpezifische Prompts mit Kontext
Z-TabellenKennt Custom-Strukturen nichtStruktur im Prompt mitgeben
Komplexe ArchitekturKann keine Gesamtarchitektur planenAufgaben in kleine Teile splitten
100% KorrektheitGenerierter Code kann Fehler enthaltenImmer testen und reviewen
PerformanceOptimiert nicht für spezifische DatenmengenPerformance-Tests durchführen
SecurityKann Sicherheitslücken einführenSecurity-Review durchführen
OfflineBenötigt Internet-VerbindungAlternative: 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 Code

Joule-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