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 Travelpersistent table ztravel " Framework escribe aquí automáticamentedraft table zdraft_travel " Para datos Draftlock master " Framework gestiona Lockstotal etag LastChangedAt " Optimistic Locking vía ETagauthorization 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 Bookingpersistent table zbookingdraft table zdraft_bookinglock dependent by _Travel " Lock del Parentauthorization 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 haceINSERT ztravel FROM ...- Framework lo haceUPDATE ztravel SET ...- Framework lo haceDELETE 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 Travellock masterauthorization 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 leerINSERT ztravel FROM ...- Debes escribirUPDATE ztravel SET ...- Debes actualizarDELETE 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
| Aspecto | Managed | Unmanaged |
|---|---|---|
| Implementación CRUD | Automático por framework | Implementar manualmente |
| Accesos a BD | Framework | Tú mismo (SELECT, INSERT, etc.) |
| Asignación de números | field ( numbering : managed ) | Manual vía cl_numberrange_runtime |
| Manejo de locks | Automático | ENQUEUE/DEQUEUE manual |
| Soporte Draft | Si (out-of-the-box) | No |
| Esfuerzo de desarrollo | Bajo (solo lógica de negocio) | Alto (todo uno mismo) |
| Flexibilidad | Limitada (reglas del framework) | Máxima (control total) |
| Integración Legacy | Difícil | Fácil (BAPIs etc.) |
| Tuning de rendimiento | Limitado | Control total |
| Mejor para | Nuevas apps Cloud | Migración Legacy |
| Curva de aprendizaje | Plana (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 Travelpersistent 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( 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_modifiedpara 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:
- 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 Travelpersistent table ztravel " ← Agregar// draft table zdraft_travel ← Opcional: Activar Draftlock master{ create; update; delete;
field ( numbering : managed ) TravelId; " ← En lugar de asignación manual // ...}- Limpiar Behavior Implementation:
" Antes (Unmanaged): Todos los métodosCLASS 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 negocioCLASS lhc_travel DEFINITION ... METHODS: " create, update, delete, read, lock → ¡ELIMINAR! " Solo lógica de negocio: validateDates FOR VALIDATE ..., setStatusNew FOR DETERMINE ..., acceptTravel FOR MODIFY ...- 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- 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 savepara 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 WORKyROLLBACK - 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
- Fundamentos RAP: /es/blog/rap-basics/
- Guía EML: /es/blog/eml-entity-manipulation-language/
- ABAP Cloud: /es/blog/abap-cloud-definition/