Контейнеры сообщений в ABAP
В ABAP реализован достаточно мощный механизм использования сообщений, в который интегрированы и локализация, и включение переменных в текст сообщений, передача этих сообщений в RFC, использование в классических и объектных исключениях, логирование, а также широкие возможности по выводу сообщений на различных вариантах GUI. При этом часто возникает необходимость передавать сообщения не поодиночке, а списком, например по завершении какого-то комплексного процесса, состоящего из шагов, либо при вызове программного модуля (RFC, BAPI и т.п.), возвращающего список сообщений.
Достаточно распространенной является практика использования таблицы со структурой BAPIRET в той или иной модификации. Как правило, таблица содержит не только сообщения, но и дополнительную информацию, как статусы, информация для логирования и другое.
Несмотря на простоту и распространенность этого решения, часто возникают похожие задачи по работе с данными из таких списков. Например получение наихудшего статуса, выбор сообщений с определенным статусом, добавление/удаление сообщений, логирование списка и прочее. Так или иначе на крупных проектах это приводит к необходимости создания некой обертки над списком сообщений, которая будет реализовывать весь функционал по работе с ним. Написание подобных классов - довольно увлекательный процесс, однако следует знать, что в стандарте уже существуют решения, способные покрыть почти все эти требования. О наиболее распространенных из них я расскажу в этой заметке.
##RECA
В стандартной поставке ABAP есть пакет RECA, включающий в себя множество утилитарных классов, решающих широкий спектр технических задач. Пакет входит в модуль RE-FX, который поставляется в составе SAP ERP, однако может быть найден и в других системах, например в EHS. Многие этих классов достойны отдельных статей, но сегодня мы поговорим об одном из них - CL_RECA_MESSAGE_LIST
, а точнее его интерфейсе IF_RECA_MESSAGE_LIST
, через который он и будет использоваться в программе.
Интерфейс имеет множество методов для работы со списком сообщений, для добавления сообщений из разных источников, для анализа сообщений и работы с логированием. Ниже я рассмотрю некоторые из них.
Инстанция создается через factory класс:
DATA:
lo_message_list TYPE REF TO if_reca_message_list.
lo_message_list = cf_reca_message_list=>create( ).
В метод можно передать параметры BAL объекта, если вы хотите сохранять в него сообщения. Также класс cf_reca_message_list
предоставляет инструментарий для поиска существующих BAL объектов.
IF_RECA_MESSAGE_LIST
позволяет добавлять сообщения из различных источников: через указание номера сообщения, из объекта исключения, из таблицы BAPIRET, из sy, а также из другого объекта сообщений.
lo_message_list->add(
id_msgty = 'S'
id_msgid = 'RDA'
id_msgno = 24
id_msgv1 = 'test'
).
*CALL BAPI
lo_message_list->add_from_bapi(
it_bapiret = lt_bapi_result
).
*CATCH cx_root INTO lo_exception.
lo_message_list->add_from_exception(
io_exception = lo_exception
).
lo_message_list->add_from_instance(
io_msglist = lo_message_list2
).
lo_message_list->add_symsg( ).
Стоит обратить внимание, что в интерфейсе нет метода, позволяющего добавлять сообщение из символьной переменной. Вероятно это связано с тем, что тексты в литералах являются плохим тоном в ABAP, так как не позволяют осуществлять перевод и затрудняют поддержку. Если вы один из тех, кто хардкодит тексты в строковые переменные, то при работе с этим объектом от такого подхода придется отказаться.
Этот контейнер удобно использовать как возвращаемый параметр метода, который выполняет цепочку действий, статус которых вы хотите в дальнейшем как-то использовать. Также удобно записывать сообщения, которые возвращают BAPI и другие ФМ, в частности те, что вызываются по RFC - зачастую они возвращают список сообщений именно в формате BAPIRET или близком к нему, который несложно сконвертировать в нужный.
DATA:
lt_message_1 TYPE bapiret1_tab,
lt_message_2 TYPE mmpur_message_list.
lo_message_list->add_from_bapi(
it_bapiret = CORRESPONDING #( lt_bapi_result )
).
lo_message_list->add_from_bapi(
it_bapiret = CORRESPONDING #( lt_message_2 MAPPING
type = msgty
id = msgid
number = msgno
message_v1 = msgv1
message_v2 = msgv2
message_v3 = msgv3
message_v4 = msgv4
)
).
Класс также позволяет получить сообщения обратно в удобном виде - получить все сообщения в виде таблицы BAPIRET, получить первое или последнее сообщение, отфильтровать сообщения по типу.
Помимо этого, класс позволяет получать различные аналитические данные по сообщениям, на основе которых удобно строить различные логигческие конструкции и проверки, не обращаясь раз за разом к самому списку напрямую. Например, можно получить статистику по всему списку сообщений, либо проверить, есть ли сообщения с определенным типом и есть ли сообщения вообще.
IF lo_message_list->has_messages_of_msgty( 'E' ).
* error processing of types E and X
ENDIF.
DATA(ls_msg_statistics) = lo_message_list->get_statistics( ).
IF ls_msg_statistics-msg_cnt_e > 0 OR ls_msg_statistics-msg_cnt_a > 0.
* error processing
ELSEIF ls_msg_statistics-msg_cnt_w > 0.
* warning processing
ELSE.
* success processing
ENDIF.
Ну и помимо прочего в контейнер встроен функционал по работе с BAL, например изменение заголовка, изменение уровня детализации сообщений и, конечно, сохранение. Пример использования:
lo_message_list = cf_reca_message_list=>create(
id_object = 'ZTEST'
id_subobject = 'SUBTEST'
id_extnumber = CONV #( sy-repid )
).
MESSAGE s000(00) INTO lv_dummy.
lo_message_list->add_symsg( ).
lo_message_list->store( ).
Для вывода сообщений класса на экран в классическом Dynpro можно воспользоваться ФМ RECA_GUI_MSGLIST_POPUP
, который выводит сообщения на стандартном экране лога.
Контейнер сообщений с подобным функционалом находит множество вариантов применения. Помимо очевидных вариантов вроде возвращаемого параметра, контейнер можно использовать, например, как атрибут класса исключения, куда можно записать несколько сообщений, описывающих возникшую ошибочную ситуацию, либо включить подробное описание шагов процесса, предшествующего ошибке. При обработке исключения эти сообщения можно включить в контейнер другого класса исключения, если обработка ошибки будет продолжена на более высоком уровне по стеку вызова, или при окончательной обработке ошибки вывести эти сообщения на экран или добавить в лог.
CLASS zcx_random_error DEFINITION
PUBLIC
INHERITING FROM cx_static_check
FINAL
CREATE PUBLIC .
PUBLIC SECTION.
INTERFACES if_t100_dyn_msg .
INTERFACES if_t100_message .
METHODS constructor
IMPORTING
textid LIKE if_t100_message=>t100key OPTIONAL
previous LIKE previous OPTIONAL
msg_container TYPE REF TO if_reca_message_list OPTIONAL.
METHODS get_msg_container
RETURNING
VALUE(ro_msg_container) TYPE REF TO if_reca_message_list .
PRIVATE SECTION.
DATA go_msg_container TYPE REF TO if_reca_message_list .
ENDCLASS.
CLASS zcx_random_error IMPLEMENTATION.
METHOD constructor.
CALL METHOD super->constructor
EXPORTING
previous = previous.
CLEAR me->textid.
IF textid IS INITIAL.
if_t100_message~t100key = if_t100_message=>default_textid.
ELSE.
if_t100_message~t100key = textid.
ENDIF.
IF msg_container IS BOUND.
go_msg_container = msg_container.
ELSE.
go_msg_container = cf_reca_message_list=>create( ).
ENDIF.
ENDMETHOD.
METHOD get_msg_container.
ro_msg_container = go_msg_container.
ENDMETHOD.
ENDCLASS.
****************************************
TRY.
* some code
lo_message_list = cf_reca_message_list=>create( ).
* fill message container in between
RAISE EXCEPTION TYPE zcx_random_error
EXPORTING
msg_container = lo_message_list.
* some other code
CATCH zcx_random_error INTO DATA(lo_error).
* add error messages to log
lo_main_log->add_from_instance( lo_error->get_msg_container( ) ).
lo_main_log->store( abap_false ).
* show messages as popup
CALL FUNCTION 'RECA_GUI_MSGLIST_POPUP'
EXPORTING
io_msglist = lo_error->get_msg_container( ).
ENDTRY.
Также работая в рамках программы или функционального модуля можно создать глобальный класс логгера, используя этот контейнер, для упрощения частого обращения к объекту для записи логов. Также можно построить вокруг контейнера класс-обертку, который будет инициализироваться с контейнером в статическом атрибуте где-то в начале программы и служить той же цели - упростить логирование, минимизировав количество boilerplate кода.
Стоит учесть, что в классе не реализованы некоторые тонкости работы с BAL, так что он не будет 100% заменой. Однако он с легкостью покрывает 99% повседневных задач по работе с сообщениями. Несмотря на то, что этот контейнер имеет довольно топорный интерфейс взаимодействия и то, что в нем смешаны несколько ответственностей - хранение сообщений, их обработка и логирование, он покрывает большинство задач, связанных с передачей сообщений внутри системы и всевозможном взаимодействии с ними.
##OData
Широкое применение концепция контейнера сообщений получила в рамках OData Gateway. SAP в рамках OData API предоставляет контейнер /IWBEP/IF_MESSAGE_CONTAINER
, который используется для передачи сообщений через OData-канал клиенту OData-сервиса. Подробную информацию можно почитать в справке по компоненту SAP_GWFND, а я приведу краткий обзору функциональности интерфейса и варианты работы с ним.
Интерфейс имплементирует класс /iwbep/cl_mgw_msg_container
, в котором также есть статический factory-метод get_mgw_msg_container
, через который возможно создать новый контейнер.
DATA:
lo_msg_container TYPE REF TO /iwbep/if_message_container.
lo_msg_container = /iwbep/cl_mgw_msg_container=>get_mgw_msg_container( ).
Этот контейнер используется в различных классах OData Gateway, в частности в качестве атрибута в базовых классах исключений /iwbep/cx_mgw_busi_exception
и /iwbep/cx_mgw_tech_exception
. Эти классы используются для возврата информации об ошибках для их последующего отображения на клиентской стороне. Я не буду подробно останавливаться на этих классах, скажу лишь, что от них удобно наследовать свои классы исключений при работе в OData сервисе и передавать с ними все сообщения об ошибках.
Теперь немного подробнее про сам контейнер.
Как и предыдущий, этот интерфейс имеет несколько методов для добавления сообщений.
lo_msg_container->add_message(
iv_msg_type = 'S'
iv_msg_id = 'RDA'
iv_msg_number = 024
iv_msg_v1 = 'test'
).
*message list from bapi
lo_msg_container->add_messages_from_bapi(
it_bapi_messages = lt_bapi_result
).
*message list from BAL
lo_msg_container->add_messages_from_log(
it_log_messages = lt_bal_messages
).
*message from another container
lo_msg_container->add_messages_from_container( lo_msg_container2 ).
* exception in CATCH-block
lo_msg_container->add_message_from_exception( lo_error ).
Так как класс заточен на работу с OData, его методы содержат параметры, позволяющие задавать различные атрибуты ответа на запрос. Например, указывать, будет ли сообщение показываться в заголовке списка сообщений, или указывать название поля/компонента, спровоцировавшего ошибку.
Обратите внимание, что метод работает только для исключений, унаследованных от /iwbep/cx_mgw_base_exception
. Остальные исключения можно обработать следующим образом - после поимки исключения выбросить новое исключение, унаследованное от /iwbep/cx_mgw_base_exception
, передав текущее ему в качестве параметра, чтобы выше по стеку вызова уже работать с исключениями этого фреймворка. Сообщение из исходного исключения добавится в список ошибок при обработке исключения в самом фреймворке.
TRY.
RAISE EXCEPTION TYPE zcx_random_error.
CATCH cx_root INTO DATA(lo_some_error).
RAISE EXCEPTION TYPE /iwbep/cx_mgw_tech_exception
EXPORTING
previous = lo_some_error.
ENDTRY.
Также в данном контейнере есть возможность добавлять сообщение из свободной текстовой переменной.
lo_msg_container->add_message_text_only(
iv_msg_type = 'E'
iv_msg_text = 'Some unexpected error!'
).
Возможно данный подход вполне оправдывает себя при отправке сообщений о технических ошибках через OData канал, но для логических (бизнес) ошибок всегда лучше использовать стандартные тексты, которые могут быть переведены на разные языки и легко найдены в коде при необходимости.
Как и предыдущий, этот контейнер обладает аналитическим функционалом.
IF lo_msg_container->get_worst_message_type( ) = 'E'.
* error processing
ELSEIF lo_msg_container->get_worst_message_type( ) = 'W'.
* not so bad
ELSE.
* perfect!
ENDIF.
Также есть возможность получать сообщения в виде таблицы типа /iwbep/t_message_container
.
Добавление сообщений при выбрасывании исключения может выглядеть следующим образом
lo_msg_container->add_message(
iv_msg_type = 'S'
iv_msg_id = 'RDA'
iv_msg_number = 123
).
RAISE EXCEPTION TYPE /iwbep/cx_mgw_busi_exception
EXPORTING
message_container = lo_msg_container
http_status_code = /iwbep/cx_mgw_busi_exception=>gcs_http_status_codes-not_found.
Контейнер не содержит функционала по работе с логом, но в данном случае это и не предполагается, для этого есть специализированные решения, вроде предыдущего контейнера.
#BOPF
Фреймворк бизнес-объектов имеет свой контейнер сообщений, адаптированный под работу с древовидной структурой модели бизнес-объектов и особенностей их функционала.
Основным контейнером сообщений является /BOBF/IF_FRW_MESSAGE
. Как и предыдущие рассмотренные примеры, контейнер создается через factory-метод соответствующего класса.
DATA:
lo_msg_container TYPE REF TO /bobf/if_frw_message.
lo_msg_container = /bobf/cl_frw_message_factory=>create_container( ).
Метод может возвращать объекты разных классов в зависимости от контекста Transaction Manager’а, но для пользователя контейнера эта разница скрыта за интерфейсом.
Контейнер имеет возможность добавлять стандартные сообщения и сообщения из других контейнеров или из классов исключений.
lo_msg_container->add( lo_msg_container2 ).
lo_msg_container->add_message(
is_msg = VALUE #(
msgty = 'S'
msgid = 'RDA'
msgno = 024
msgv1 = 'test'
)
).
lo_msg_container->add_exception( lo_error ).
При добавлении сообщения можно указать специфичные для BOPF параметры - место возникновения сообщения: ключ ноды БО, ключ конкретной инстанции этой ноды, атрибут этой ноды, который содержит ошибочные данные.
Однако основной функционал этого контейнера реализован в рамках другого подхода. В BOPF используются объектно-ориентированные сообщения. В самом контейнере есть два метода по добавлению объектно-ориентированных сообщений - простое добавление сообщения и добавления сообщения с явным указанием места возникновения сообщения (location). Намного интереснее поговорить про сами классы сообщений, но об этом чуть позже.
Как и остальные контейнеры, /BOBF/IF_FRW_MESSAGE
имеет методы для получения списка сообщений - возможно либо получить все сообщения таблицей, либо задать определенный фильтр.
DATA:
lt_message_list TYPE /bobf/cm_frw=>tt_frw,
lt_detailed_msg_list TYPE /bobf/t_frw_message_k.
*get all messages as object list
lo_msg_container->get(
IMPORTING
et_message = lt_message_list
).
*get error messages as detailed list
lo_msg_container->get_messages(
EXPORTING
iv_severity = /bobf/cm_frw=>co_severity_success
IMPORTING
et_message = lt_detailed_msg_list
).
Также есть возможность проверить наличие сообщений с ошибками. Дополнительно можно указать, включать ли в проверку сообщения из actions или determinations бизнес-объектов. По-умолчанию проверяются все.
IF lo_msg_container->check( ).
* error processing
ENDIF.
На этом основной функционал контейнера заканчивается. Куда интереснее подробнее рассмотреть концепцию сообщений, реализованную в BOPF.
###Объектно-ориентированные сообщения, используемые в BOPF.
Как уже было сказано выше, бизнес-объекты используют для обработки сообщений объекты, наследующиеся от абстрактного класса /BOBF/CM_FRW
. Технически он наследует класс CX_DYNAMIC_CHECK
, то есть представляет собой класс исключений. Технически. (Подробнее)
Смысл объектной обертки заключается в переходе от мета-сущности сообщений к объектам, которые удобно использовать в рамках программы (если, конечно, вы не из староверов, сидящих на процедурной парадигме). Такие сообщения довольно удобно создавать и использовать. Например, класс /BOBF/CM_FRW_SYMSG
, который имеет интерфейс для добавления сообщения в привычном виде.
DATA(lo_message) = NEW /bobf/cm_frw_symsg(
textid = VALUE #(
msgid = 'RDA'
msgno = 024
attr1 = 'test'
)
severity = 'E'
)
).
Их удобно добавлять в соответствующий контейнер.
lo_msg_container->add_cm( lo_message ).
lo_msg_container->add_cm(
NEW /bobf/cm_frw_symsg(
textid = VALUE #(
msgid = 'RDA'
msgno = 015
attr1 = 'one'
attr2 = 'two'
)
severity = 'S'
)
).
MESSAGE e024(rda) WITH 'example' INTO DATA(lv_dummy).
lo_msg_container->add_cm(
NEW /bobf/cm_frw_symsg(
textid = VALUE #(
msgid = sy-msgid
msgno = sy-msgno
attr1 = sy-msgv1
)
severity = sy-msgty
)
).
Можно добавлять текст сообщений из строковой переменной (хоть я и считаю это и не очень хорошей практикой).
lo_msg_container->add_cm(
NEW /bobf/cm_frw_symsg(
message_text = 'some custom text'
)
).
Также сообщения можно использовать как и обычные классы исключений, а при обработке на более высоком уровне добавить в контейнер. Особенно это удобно для того, чтобы обернуть сообщения от вызовов ФМ, используя соответствующий синтаксис 7.5 (не забудьте про интерфейс IF_T100_DYN_MSG
).
TRY.
* some coding
RAISE EXCEPTION TYPE zcm_hello
MESSAGE
ID sy-msgid
TYPE sy-msgty
NUMBER sy-msgno
WITH sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4.
* more coding
RAISE EXCEPTION TYPE zcm_hello
EXPORTING
textid = zcm_hello=>some_error
severity = zcm_hello=>co_severity_error.
* some coding after all
CATCH /bobf/cm_frw INTO DATA(lo_message).
lo_msg_container->add_cm( lo_message ).
CATCH cx_root INTO DATA(lo_error).
MESSAGE lo_error TYPE 'X'.
ENDTRY.
Так как эти сообщения имплементируют интерфейс IF_MESSAGE
, их можно использовать для отображения на экране при помощи оператора MESSAGE (хотя, если вы работаете в рамках BOPF, скорее всего интерфейс реализован на WebDynpro или Fiori, нежели на SAPGUI).
При создании объекта сообщения можно указать дополнительные параметры - как уже известное нам место возникновения сообщения в BOPF, так и другие - SEVERITY - тип сообщения, SYMPTOM - описывает причину возникновения ошибки, LIFETIME - описывает категорию сообщения (временные или постоянные: первые просто возвращаются пользователю и забываются, вторые сохраняются вместе с моделью (релевангны только для временных (draft) БО и будут появляться на последующих шагах обработки, пока проблема не будет устранена).
lo_msg_container->add_cm(
NEW /bobf/cm_frw_symsg(
textid = ls_textid
severity = /bobf/cm_frw=>co_severity_warning
symptom = /bobf/if_frw_message_symptoms=>co_bo_inconsistency
lifetime = /bobf/cm_frw=>co_lifetime_transition
)
).
Несмотря на простоту и явность интерфейса объектных сообщений, он достаточно громоздок. Поэтому для упрощения рутинного кода в модулях, активно использующих BOPF, реализованы утилитарные классы для выполнения повторяющихся действий с контейнерами и сообщениями. Например в TM это /SCMTMS/CL_MSG_HELPER
, а в EHS это CL_EHFND_FW_APPL_LOG_HELPER
. Наверняка подобные есть и в других модулях, реализованных на бизнес-объектах - SLC, QIM, MOC и т.д. - найдите их самостоятельно. А если вы пришли на проект, где на бизнес-объектах реализована кастомерская логика с нуля (как на ISM-PrIMa в Deutsche Bahn), вы наверняка найдете с любовью написанный до вас Z-хелпер.
Для классов сообщений действует особый нейминг - *cm*, т.е. вы можете называть свои классы z*cm* или y*cm* (или /*/cm*, если вы работаете в вендорском пространстве имен). Семантически они ничем не отличаются от других классов исключений, и так же, как обычные классы исключений, могут иметь список сообщений, который можно редактировать через конструктор в se24 или вручную. Можно, например, добавить все сообщения какого-то модуля в один такой класс, а при создании сообщения передавать уже константу в textid - это сделает код более чистым, а сами сообщения можно будет найти через where-used list (работает только для глобальных классов).
CLASS zcm_hello DEFINITION
PUBLIC
INHERITING FROM /bobf/cm_frw
CREATE PUBLIC .
PUBLIC SECTION.
INTERFACES if_t100_dyn_msg .
CONSTANTS:
BEGIN OF some_error,
msgid TYPE symsgid VALUE 'RDA',
msgno TYPE symsgno VALUE '024',
attr1 TYPE scx_attrname VALUE '',
attr2 TYPE scx_attrname VALUE '',
attr3 TYPE scx_attrname VALUE '',
attr4 TYPE scx_attrname VALUE '',
END OF some_error .
METHODS constructor
IMPORTING
!textid LIKE if_t100_message=>t100key OPTIONAL
!previous LIKE previous OPTIONAL
!severity TYPE ty_message_severity OPTIONAL
!symptom TYPE ty_message_symptom OPTIONAL
!lifetime TYPE ty_message_lifetime DEFAULT co_lifetime_transition
!ms_origin_location TYPE /bobf/s_frw_location OPTIONAL
!mt_environment_location TYPE /bobf/t_frw_location OPTIONAL
!mv_act_key TYPE /bobf/act_key OPTIONAL
!mv_assoc_key TYPE /bobf/obm_assoc_key OPTIONAL
!mv_bopf_location TYPE /bobf/conf_key OPTIONAL
!mv_det_key TYPE /bobf/det_key OPTIONAL
!mv_query_key TYPE /bobf/obm_query_key OPTIONAL
!mv_val_key TYPE /bobf/val_key OPTIONAL .
PROTECTED SECTION.
PRIVATE SECTION.
ENDCLASS.
CLASS zcm_hello IMPLEMENTATION.
METHOD constructor.
CALL METHOD super->constructor
EXPORTING
previous = previous
severity = severity
symptom = symptom
lifetime = lifetime
ms_origin_location = ms_origin_location
mt_environment_location = mt_environment_location
mv_act_key = mv_act_key
mv_assoc_key = mv_assoc_key
mv_bopf_location = mv_bopf_location
mv_det_key = mv_det_key
mv_query_key = mv_query_key
mv_val_key = mv_val_key.
CLEAR me->textid.
IF textid IS INITIAL.
if_t100_message~t100key = if_t100_message=>default_textid.
ELSE.
if_t100_message~t100key = textid.
ENDIF.
ENDMETHOD.
ENDCLASS.
Сгенерированный класс
Однако, несмотря на красоту описанного подхода, пока что он находит применение только внутри BOPF, в котором в принципе существует “своя атмосфера”. Поэтому для интеграции с другими частями SAP необходимо как-то преобразовать объектные сообщения в сообщения старой школы. Обычно такой функционал уже реализован в классах-хелперах модуля.
Несмотря на некоторые накладные расходы, сама концепция очень элегантна и позволяет работать с сообщениями в удобном формате, сочетающимся с современным стремлением ABAP стать нормальным языком программирования.
##Реализация в разных компонентах
Как мы все знаем, работники SAP никогда не общаются друг с другом, ходят в бар в разное время, а за обмен информацией между проектами можно положить учетку на стол и до конца жизни писать только в Z*. По крайней мере этим можно было бы объяснить такое огромное количество разнообразных реализаций одного и того же, как в различных продуктах этой компании. Каждый модуль, каждый фреймворк и технология зачастую имеют собственные реализации одних и тех же базовых либо более специфических вещей, которые не совместимы друг с другом.
Не обошла стороной эта тенденция и контейнеры сообщений. Свои контейнеры есть у различных модулей и фреймворков. Вот несколько тех, которые я встречал в работе:
CL_LOG_PPF
- контейнер PPFCL_EHFND_FW_LOGGER
- контейнер ENA (объектная обертка над BOPF) в EHS
Есть и другие, вы можете встретить их при работе с разными частями SAP. Но суть будет везде примерно одинакова, разве что адаптирована под контекст выполняемых задач.
##Что же выбрать?
В итоге мы имеем дюжину контейнеров сообщений с различным функционалом. Какой же лучше? Какой использовать в повседневной разработке?
Скорее всего, на этот вопрос нельзя дать однозначного ответа. Каждый хорош по-своему, поэтому используйте тот, который близок к контексту вашей разработки. Если вы разрабатываете OData-сервис, логично будет построить обмен сообщениями в рамках контейнера /IWBEP/IF_MESSAGE_CONTAINER
и классов исключений, используемых в фреймворке. Аналогично и для других фреймворков или модулей - BOPF, PPF и т.д. В других случаях я советую использовать IF_RECA_MESSAGE_LIST
, как наиболее универсальный. Если же в вашем модуле его нет (например в EWM - нет), поищите какую-то местную реализацию, скорее всего она там есть, если модуль моложе пятнадцати лет. Если и такого нет, воспользуйтесь каким-нибудь готовым Z-решением, благо их есть несколько довольно хороших. В любом случае такая обертка будет гораздо удобнее в работе, чем таскание туда-сюда таблиц вроде BAPIRET.
Стремитесь к чистому объектно-ориентированному коду с минимумом неявных операций, коими являются многие операции с сообщениями, вроде классических исключений. Контейнеры сообщений помогут решить эту задачу элегантным и удобным способом, а также помогут использовать информацию из списка сообщений легким для разработчика путем.
comments