RAP Managed vs Unmanaged: Cuándo usar cada escenario

Kategorie
ABAP-Statements
Veröffentlicht
Autor
Johannes

Managed vs. Unmanaged es la decisión arquitectónica central en RAP (RESTful ABAP Programming). Determina quién asume el control de transacciones y las operaciones CRUD: el framework RAP (Managed) o tú mismo (Unmanaged).

La pregunta fundamental

" ¿Quién ejecuta CREATE, UPDATE, DELETE?
" Managed: "¡SAP, hazlo tú!"
managed implementation in class zbp_i_travel unique;
" Unmanaged: "¡Lo hago yo mismo!"
unmanaged implementation in class zbp_i_travel unique;

Escenario Managed: El framework hace el trabajo

¿Cuándo usar Managed?

Perfecto para:

  • Nuevas aplicaciones (desarrollo Greenfield)
  • Procesos de negocio estándar sin lógica legacy compleja
  • Apps Fiori transaccionales con operaciones CRUD
  • Cuando necesitas un BO funcional rápidamente
  • Funcionalidad Draft (almacenamiento intermedio)

No adecuado para:

  • Integración con código legacy (Funciones, BAPIs)
  • Lógica de transacciones compleja fuera de RAP
  • Cuando necesitas control total sobre accesos a BD
  • Migración de Dynpro/Web Dynpro con comportamiento existente

Managed: Behavior Definition

managed implementation in class zbp_i_travel unique;
strict ( 2 );
with draft; " ¡Draft solo disponible en Managed!
define behavior for ZI_Travel alias Travel
persistent table ztravel " Framework escribe aquí automáticamente
draft table zdraft_travel " Para datos Draft
lock master " Framework gestiona Locks
total etag LastChangedAt " Optimistic Locking vía ETag
authorization master ( instance )
{
// CRUD: ¡Solo declarar, no necesita implementación!
create;
update;
delete;
// Campos: Framework se encarga del mapping
field ( readonly ) TravelId;
field ( readonly ) CreatedBy, CreatedAt, LastChangedBy, LastChangedAt;
field ( numbering : managed ) TravelId; " ¡Auto-numeración!
// Lógica de negocio: Aquí implementas
validation validateDates on save { field BeginDate, EndDate; }
determination setStatusNew on modify { create; }
action acceptTravel result [1] $self;
// Draft Actions: Framework las proporciona automáticamente
draft action Edit;
draft action Activate optimized;
draft action Discard;
draft action Resume;
// Asociaciones
association _Bookings { create; with draft; }
}
define behavior for ZI_Booking alias Booking
persistent table zbooking
draft table zdraft_booking
lock dependent by _Travel " Lock del Parent
authorization dependent by _Travel
{
update;
delete;
field ( readonly ) TravelId, BookingId;
field ( numbering : managed ) BookingId;
association _Travel { with draft; }
}

Managed: Behavior Implementation

CLASS lhc_travel DEFINITION INHERITING FROM cl_abap_behavior_handler.
PRIVATE SECTION.
METHODS:
" ¡Solo implementar lógica de negocio!
" Validation: Se ejecuta en Save
validateDates FOR VALIDATE ON SAVE
IMPORTING keys FOR Travel~validateDates,
" Determination: Establecer valores automáticos
setStatusNew FOR DETERMINE ON MODIFY
IMPORTING keys FOR Travel~setStatusNew,
" Action: Operación de negocio
acceptTravel FOR MODIFY
IMPORTING keys FOR ACTION Travel~acceptTravel RESULT result.
" NO implementar: get_global_authorizations, read, create, update, delete
" → ¡Framework lo hace automáticamente!
ENDCLASS.
CLASS lhc_travel IMPLEMENTATION.
METHOD validateDates.
" Framework YA leyó datos → solo validar
READ ENTITIES OF zi_travel IN LOCAL MODE
ENTITY Travel
FIELDS ( BeginDate EndDate )
WITH CORRESPONDING #( keys )
RESULT DATA(lt_travel).
LOOP AT lt_travel INTO DATA(ls_travel).
" Verificar regla de negocio
IF ls_travel-EndDate < ls_travel-BeginDate.
" Llenar estructura de framework para errores
APPEND VALUE #(
%tky = ls_travel-%tky
%element-EndDate = if_abap_behv=>mk-on
) TO failed-travel.
APPEND VALUE #(
%tky = ls_travel-%tky
%element-EndDate = if_abap_behv=>mk-on
%msg = new_message_with_text(
severity = if_abap_behv_message=>severity-error
text = 'La fecha fin debe ser posterior a la fecha inicio'
)
) TO reported-travel.
ENDIF.
ENDLOOP.
ENDMETHOD.
METHOD setStatusNew.
" Framework creó Entity → establecer valores por defecto
READ ENTITIES OF zi_travel IN LOCAL MODE
ENTITY Travel
FIELDS ( Status )
WITH CORRESPONDING #( keys )
RESULT DATA(lt_travel).
" Solo los nuevos (Status inicial) a 'O' (Open)
MODIFY ENTITIES OF zi_travel IN LOCAL MODE
ENTITY Travel
UPDATE FIELDS ( Status )
WITH VALUE #( FOR travel IN lt_travel WHERE ( Status IS INITIAL )
( %tky = travel-%tky
Status = 'O' ) )
REPORTED DATA(reported_modify).
ENDMETHOD.
METHOD acceptTravel.
" Action = Operación de negocio (no solo un Update)
MODIFY ENTITIES OF zi_travel IN LOCAL MODE
ENTITY Travel
UPDATE FIELDS ( Status LastChangedAt )
WITH VALUE #( FOR key IN keys
( %tky = key-%tky
Status = 'A'
LastChangedAt = cl_abap_context_info=>get_system_date( ) ) )
FAILED failed
REPORTED reported.
" Devolver resultado (result [1] $self)
READ ENTITIES OF zi_travel IN LOCAL MODE
ENTITY Travel
ALL FIELDS
WITH CORRESPONDING #( keys )
RESULT result.
ENDMETHOD.
ENDCLASS.

Lo que NO necesitas escribir:

  • SELECT * FROM ztravel - Framework lo hace
  • INSERT ztravel FROM ... - Framework lo hace
  • UPDATE ztravel SET ... - Framework lo hace
  • DELETE FROM ztravel - Framework lo hace
  • Manejo de locks - Framework lo hace
  • Asignación de números - Framework lo hace (con numbering : managed)

Escenario Unmanaged: Tienes control total

¿Cuándo usar Unmanaged?

Perfecto para:

  • Integración Legacy (incorporar BAPIs, módulos de función)
  • Lógica de transacciones compleja (commits multinivel)
  • Migrar desde programas Dynpro/Web-Dynpro existentes
  • Cuando necesitas operaciones de BD especiales (ej. Native SQL)
  • Mecanismos de lock personalizados

No adecuado para:

  • Prototipado rápido
  • CRUD estándar sin particularidades
  • Funcionalidad Draft (¡no disponible en Unmanaged!)

Unmanaged: Behavior Definition

unmanaged implementation in class zbp_i_travel unique;
strict ( 2 );
define behavior for ZI_Travel alias Travel
lock master
authorization master ( instance )
etag master LastChangedAt
{
// CRUD: ¡Todo debe implementarse!
create;
update;
delete;
// Read TAMBIÉN debe implementarse (¡diferente a Managed!)
// Lógica de negocio como en Managed
validation validateDates on save { field BeginDate, EndDate; }
determination setStatusNew on modify { create; }
action acceptTravel result [1] $self;
// Manejo de Lock debes implementarlo TÚ
lock ( lock_key );
}

Unmanaged: Behavior Implementation

CLASS lhc_travel DEFINITION INHERITING FROM cl_abap_behavior_handler.
PRIVATE SECTION.
METHODS:
" Implementar TODO uno mismo:
" Create: Crear nuevas entities
create FOR MODIFY
IMPORTING entities FOR CREATE Travel,
" Update: Modificar entities existentes
update FOR MODIFY
IMPORTING entities FOR UPDATE Travel,
" Delete: Eliminar entities
delete FOR MODIFY
IMPORTING keys FOR DELETE Travel,
" Read: Leer entities
read FOR READ
IMPORTING keys FOR READ Travel RESULT result,
" Lock: Gestionar bloqueos
lock FOR LOCK
IMPORTING keys FOR LOCK Travel,
" Feature Control
get_instance_features FOR INSTANCE FEATURES
IMPORTING keys REQUEST requested_features FOR Travel RESULT result,
" Lógica de negocio (como en Managed)
validateDates FOR VALIDATE ON SAVE
IMPORTING keys FOR Travel~validateDates,
setStatusNew FOR DETERMINE ON MODIFY
IMPORTING keys FOR Travel~setStatusNew,
acceptTravel FOR MODIFY
IMPORTING keys FOR ACTION Travel~acceptTravel RESULT result.
ENDCLASS.
CLASS lhc_travel IMPLEMENTATION.
METHOD create.
" Extraer datos del parámetro entities
DATA lt_travel TYPE TABLE FOR CREATE zi_travel.
lt_travel = entities.
" Asignación de números (¡MANUAL, ya que es unmanaged!)
LOOP AT lt_travel ASSIGNING FIELD-SYMBOL(<fs_travel>).
" Obtener número del rango de números
TRY.
<fs_travel>-TravelId = cl_numberrange_runtime=>get_next_number(
nr_range_nr = '01'
object = 'ZTRAVEL'
).
CATCH cx_number_ranges INTO DATA(lx_nr).
" Manejar error
APPEND VALUE #(
%cid = <fs_travel>-%cid
%fail-cause = if_abap_behv=>cause-unspecific
) TO failed-travel.
CONTINUE.
ENDTRY.
" Establecer valores por defecto
<fs_travel>-CreatedBy = sy-uname.
<fs_travel>-CreatedAt = cl_abap_context_info=>get_system_date( ).
<fs_travel>-Status = 'O'.
" Escribir en BD (¡MANUAL!)
INSERT ztravel FROM @( CORRESPONDING #( <fs_travel> ) ).
IF sy-subrc = 0.
" Devolver mapping exitoso
APPEND VALUE #(
%cid = <fs_travel>-%cid
TravelId = <fs_travel>-TravelId
) TO mapped-travel.
ELSE.
" Error
APPEND VALUE #(
%cid = <fs_travel>-%cid
%fail-cause = if_abap_behv=>cause-unspecific
) TO failed-travel.
ENDIF.
ENDLOOP.
ENDMETHOD.
METHOD update.
" Extraer campos a actualizar
LOOP AT entities INTO DATA(ls_entity).
" %control verifica qué campos deben modificarse
IF ls_entity-%control-Status = if_abap_behv=>mk-on.
" Status fue cambiado → DB-Update
UPDATE ztravel
SET status = @ls_entity-Status,
last_changed_by = @sy-uname,
last_changed_at = @cl_abap_context_info=>get_system_date( )
WHERE travel_id = @ls_entity-TravelId.
IF sy-subrc <> 0.
APPEND VALUE #(
%tky = ls_entity-%tky
%fail-cause = if_abap_behv=>cause-not_found
) TO failed-travel.
ENDIF.
ENDIF.
" Más campos análogamente...
IF ls_entity-%control-Description = if_abap_behv=>mk-on.
UPDATE ztravel SET description = @ls_entity-Description
WHERE travel_id = @ls_entity-TravelId.
ENDIF.
ENDLOOP.
ENDMETHOD.
METHOD delete.
" Eliminar de BD
DELETE FROM ztravel
WHERE travel_id IN ( SELECT TravelId FROM @keys AS k ).
IF sy-dbcnt < lines( keys ).
" No todos eliminados → Error
LOOP AT keys INTO DATA(ls_key).
SELECT SINGLE @abap_true FROM ztravel
WHERE travel_id = @ls_key-TravelId
INTO @DATA(lv_exists).
IF lv_exists = abap_true.
" Aún existe → no pudo eliminarse
APPEND VALUE #(
%tky = ls_key-%tky
%fail-cause = if_abap_behv=>cause-locked
) TO failed-travel.
ENDIF.
ENDLOOP.
ENDIF.
ENDMETHOD.
METHOD read.
" Leer datos de BD
SELECT * FROM ztravel
FOR ALL ENTRIES IN @keys
WHERE travel_id = @keys-TravelId
INTO TABLE @DATA(lt_db_travel).
" Convertir a estructura RAP
result = CORRESPONDING #( lt_db_travel MAPPING TO ENTITY ).
ENDMETHOD.
METHOD lock.
" Bloquear Lock-Object (vía ENQUEUE)
LOOP AT keys INTO DATA(ls_key).
CALL FUNCTION 'ENQUEUE_EZTRAVEL'
EXPORTING
mode_ztravel = 'E'
mandt = sy-mandt
travel_id = ls_key-TravelId
EXCEPTIONS
foreign_lock = 1
system_failure = 2
OTHERS = 3.
IF sy-subrc <> 0.
" Lock falló
APPEND VALUE #(
%tky = ls_key-%tky
%fail-cause = if_abap_behv=>cause-locked
) TO failed-travel.
ENDIF.
ENDLOOP.
ENDMETHOD.
METHOD get_instance_features.
" Feature Control (como en Managed)
READ ENTITIES OF zi_travel IN LOCAL MODE
ENTITY Travel
FIELDS ( Status )
WITH CORRESPONDING #( keys )
RESULT DATA(lt_travel).
result = VALUE #( FOR travel IN lt_travel
( %tky = travel-%tky
%features-%action-acceptTravel = COND #(
WHEN travel-Status = 'O' THEN if_abap_behv=>fc-o-enabled
ELSE if_abap_behv=>fc-o-disabled
)
)
).
ENDMETHOD.
METHOD validateDates.
" Idéntico a Managed
" ... (ver ejemplo Managed arriba)
ENDMETHOD.
METHOD setStatusNew.
" Idéntico a Managed
" ... (ver ejemplo Managed arriba)
ENDMETHOD.
METHOD acceptTravel.
" Idéntico a Managed
" ... (ver ejemplo Managed arriba)
ENDMETHOD.
ENDCLASS.

Lo que DEBES escribir tú mismo:

  • SELECT * FROM ztravel - Debes leer
  • INSERT ztravel FROM ... - Debes escribir
  • UPDATE ztravel SET ... - Debes actualizar
  • DELETE FROM ztravel - Debes eliminar
  • Manejo de locks - Debes bloquear/desbloquear
  • Asignación de números - Debes llamar rangos de números

Comparación: Managed vs. Unmanaged

AspectoManagedUnmanaged
Implementación CRUDAutomático por frameworkImplementar manualmente
Accesos a BDFrameworkTú mismo (SELECT, INSERT, etc.)
Asignación de númerosfield ( numbering : managed )Manual vía cl_numberrange_runtime
Manejo de locksAutomáticoENQUEUE/DEQUEUE manual
Soporte DraftSi (out-of-the-box)No
Esfuerzo de desarrolloBajo (solo lógica de negocio)Alto (todo uno mismo)
FlexibilidadLimitada (reglas del framework)Máxima (control total)
Integración LegacyDifícilFácil (BAPIs etc.)
Tuning de rendimientoLimitadoControl total
Mejor paraNuevas apps CloudMigración Legacy
Curva de aprendizajePlana (menos código)Empinada (mucho código)

Escenario híbrido: Managed save + Unmanaged side effects

Problema: Quieres usar Managed, pero necesitas lógica personalizada después del Save (ej. llamar API externa).

Solución: Hook save_modified en escenario Managed:

managed implementation in class zbp_i_travel unique;
strict ( 2 );
with additional save; " ← Activar hook
define behavior for ZI_Travel alias Travel
persistent table ztravel
{
create;
update;
delete;
// ...
}

Implementación:

CLASS lsc_travel DEFINITION INHERITING FROM cl_abap_behavior_saver.
PROTECTED SECTION.
METHODS:
" save_modified: DESPUÉS de Framework-Save, ANTES de COMMIT final
save_modified REDEFINITION,
" cleanup_finalize: DESPUÉS de COMMIT (o ROLLBACK)
cleanup_finalize REDEFINITION.
ENDCLASS.
CLASS lsc_travel IMPLEMENTATION.
METHOD save_modified.
" Framework YA escribió en BD (pero aún no committed)
" Ahora puedes implementar Side-Effects:
" Ejemplo: Enviar email por cada Travel nuevo
IF create-travel IS NOT INITIAL.
LOOP AT create-travel INTO DATA(ls_created).
" Llamar API de email (ver /email-sending/)
TRY.
cl_email_sender=>send_notification(
recipient = '[email protected]'
subject = |Nuevo viaje { ls_created-TravelId } creado|
body = |Viaje de { ls_created-BeginDate } a { ls_created-EndDate }|
).
CATCH cx_send_req_bcs INTO DATA(lx_email).
" Loguear error, pero NO abortar transacción
cl_bali_log=>create( )->add_item( cl_bali_message_setter=>create_from_exception( lx_email ) )->save( ).
ENDTRY.
ENDLOOP.
ENDIF.
" Ejemplo: Llamar API externa para Updates
IF update-travel IS NOT INITIAL.
LOOP AT update-travel INTO DATA(ls_updated).
" HTTP-Call a sistema externo (ver /http-client/)
DATA(lo_http) = cl_web_http_client_manager=>create_by_http_destination( ... ).
" ... enviar HTTP Request
ENDLOOP.
ENDIF.
ENDMETHOD.
METHOD cleanup_finalize.
" DESPUÉS de COMMIT o ROLLBACK
" Aquí puedes implementar lógica de cleanup
" (ej. eliminar archivos temporales, cerrar conexiones)
ENDMETHOD.
ENDCLASS.

¿Cuándo usar?

  • Managed para CRUD estándar + operaciones de BD
  • save_modified para Side-Effects (Email, APIs externas, Logging)
  • Todas las features del framework (Draft, Numbering, etc.) se mantienen

Árbol de decisión

┌─────────────────────────────────────────────────┐
│ ¿Nueva aplicación (Greenfield)? │
└──┬────────────────────────────────────────┬─────┘
│ Sí │ No
▼ ▼
┌─────────────────────────────┐ ┌─────────────────────────────┐
│ ¿CRUD estándar suficiente? │ │ ¿Integrar sistema Legacy? │
└──┬──────────────────────┬───┘ └──┬──────────────────────┬───┘
│ Sí │ No │ Sí │ No
▼ ▼ ▼ ▼
┌──────────┐ ┌──────────────┐ ┌──────────┐ ┌──────────────┐
│ MANAGED │ │ ¿Operaciones │ │UNMANAGED │ │ ¿Transacción │
│ │ │ BD complejas?│ │ │ │ especial? │
│ │ └──┬───────┬───┘ │ │ └──┬───────┬───┘
└──────────┘ │ Sí │ No └──────────┘ │ Sí │ No
▼ ▼ ▼ ▼
┌──────────┐ ┌────────────┐ ┌──────────┐ ┌──────────┐
│UNMANAGED │ │ MANAGED + │ │UNMANAGED │ │ MANAGED │
│ │ │save_modified│ │ │ │ │
└──────────┘ └────────────┘ └──────────┘ └──────────┘

Migración: De Unmanaged a Managed

Escenario: Tienes un BO Unmanaged y quieres migrar a Managed.

Pasos:

  1. Ajustar Behavior Definition:
" Antes:
unmanaged implementation in class zbp_i_travel unique;
" Después:
managed implementation in class zbp_i_travel unique;
" Adicionalmente:
define behavior for ZI_Travel alias Travel
persistent table ztravel " ← Agregar
// draft table zdraft_travel ← Opcional: Activar Draft
lock master
{
create;
update;
delete;
field ( numbering : managed ) TravelId; " ← En lugar de asignación manual
// ...
}
  1. Limpiar Behavior Implementation:
" Antes (Unmanaged): Todos los métodos
CLASS lhc_travel DEFINITION ...
METHODS:
create FOR MODIFY ...,
update FOR MODIFY ...,
delete FOR MODIFY ...,
read FOR READ ...,
lock FOR LOCK ...,
validateDates FOR VALIDATE ...,
// etc.
" Después (Managed): Solo mantener lógica de negocio
CLASS lhc_travel DEFINITION ...
METHODS:
" create, update, delete, read, lock → ¡ELIMINAR!
" Solo lógica de negocio:
validateDates FOR VALIDATE ...,
setStatusNew FOR DETERMINE ...,
acceptTravel FOR MODIFY ...
  1. Eliminar accesos a BD:
" Antes (Unmanaged):
METHOD create.
INSERT ztravel FROM @( CORRESPONDING #( entities ) ).
" ...
ENDMETHOD.
" Después (Managed): ¡Eliminar método completamente!
" Framework hace INSERT automáticamente
  1. Asignación de números:
" Antes (Unmanaged):
<fs_travel>-TravelId = cl_numberrange_runtime=>get_next_number( ... ).
" Después (Managed):
" En BDEF: field ( numbering : managed ) TravelId;
" → Framework asigna automáticamente desde secuencia de clave de `persistent table ztravel`

Notas importantes / Mejores prácticas

  • Default = Managed: Usa Managed, a menos que tengas una buena razón para Unmanaged
  • Draft requiere Managed: La funcionalidad Draft SOLO está disponible en Managed
  • Unmanaged para Legacy: Si necesitas integrar BAPIs/FuBas → Unmanaged
  • Híbrido posible: managed + with additional save para Side-Effects
  • Rendimiento: Managed NO es más lento - Framework está optimizado
  • Testing: Managed es más fácil de testear (menos código = menos errores)
  • Lock-Objects: En Unmanaged debes crear Lock-Objects (SE11: ENQUEUE_*) tú mismo
  • Number Ranges: En Unmanaged debes gestionar Number Ranges (SNRO) tú mismo
  • Transacciones: Unmanaged da control total sobre COMMIT WORK y ROLLBACK
  • Migración: De Unmanaged → Managed es más costoso que al revés
  • Documentación: Justifica la decisión Unmanaged (para futuros desarrolladores)
  • IN LOCAL MODE: Usar en AMBOS escenarios para código de Behavior-Implementation
  • Usar EML: También en Unmanaged deberías usar EML para accesos a BO (ver Guía EML)

Recursos adicionales