DAO — Data Access Object
DAO (Data Access Object) — паттерн, що інкапсулює всю роботу з БД для конкретної сутності в один клас за інтерфейсом. Бізнес-логіка не знає, де зберігаються дані — в ABAP SQL, в AMDP, в REST-API чи в тестовій памʼяті. Вона просить DAO: get, save, delete.
Для нетривіального читання/запису в ентерпрайз-коді це обовʼязковий шар: не роби SELECT напряму з бізнес-класу.
Коли застосовувати
Section titled “Коли застосовувати”- Бізнес-логіка має тестуватись без БД — підсовуємо
zcl_user_dao_in_memoryзамістьzcl_user_dao_db. - Одну сутність читають з кількох місць — дублювання
SELECTстає ризиком. - Доступ до сутності може змінитись (БД → REST, on-prem → cloud) — зміна локалізується в одному класі.
- Треба централізувати перевірки повноважень, валідацію, кеш — все в DAO, не розмазано.
ABAP-реалізація
Section titled “ABAP-реалізація”Контракт
Section titled “Контракт”INTERFACE zif_user_dao.
METHODS: get_by_id IMPORTING iv_id TYPE zty_user_id RETURNING VALUE(rs_user) TYPE zty_user RAISING zcx_user_not_found,
find_by_email IMPORTING iv_email TYPE string RETURNING VALUE(rs_user) TYPE zty_user RAISING zcx_user_not_found,
save IMPORTING is_user TYPE zty_user RAISING zcx_save_failed,
delete IMPORTING iv_id TYPE zty_user_id RAISING zcx_user_not_found.
ENDINTERFACE.Правила контракту:
- Метод повертає DTO або тип БД-таблиці, ніколи внутрішні cursor-и / itab з проміжним станом.
- Помилки — винятки-класи, не
sy-subrcабоEXPORTING ev_error. Винятки кажуть більше. - Немає «читання з блокуванням» як окремого метода — блокування — це інфраструктура (SAP enqueue), не частина DAO.
- Немає методів, що приймають
WHERE-рядки. Специфічні запити — окремі метoди (find_by_email,list_active).
Реалізація на ABAP SQL
Section titled “Реалізація на ABAP SQL”CLASS zcl_user_dao_db DEFINITION PUBLIC FINAL CREATE PUBLIC. PUBLIC SECTION. INTERFACES zif_user_dao.ENDCLASS.
CLASS zcl_user_dao_db IMPLEMENTATION.
METHOD zif_user_dao~get_by_id. SELECT SINGLE id, email, name, is_active FROM zusers WHERE id = @iv_id INTO CORRESPONDING FIELDS OF @rs_user. IF sy-subrc <> 0. RAISE EXCEPTION NEW zcx_user_not_found( iv_id = iv_id ). ENDIF. ENDMETHOD.
METHOD zif_user_dao~find_by_email. SELECT SINGLE id, email, name, is_active FROM zusers WHERE email = @iv_email INTO CORRESPONDING FIELDS OF @rs_user. IF sy-subrc <> 0. RAISE EXCEPTION NEW zcx_user_not_found( iv_email = iv_email ). ENDIF. ENDMETHOD.
METHOD zif_user_dao~save. MODIFY zusers FROM @( CORRESPONDING zusers( is_user ) ). IF sy-subrc <> 0. RAISE EXCEPTION NEW zcx_save_failed( iv_id = is_user-id ). ENDIF. ENDMETHOD.
METHOD zif_user_dao~delete. DELETE FROM zusers WHERE id = @iv_id. IF sy-dbcnt = 0. RAISE EXCEPTION NEW zcx_user_not_found( iv_id = iv_id ). ENDIF. ENDMETHOD.
ENDCLASS.Реалізація in-memory — для тестів
Section titled “Реалізація in-memory — для тестів”CLASS zcl_user_dao_in_memory DEFINITION PUBLIC FINAL CREATE PUBLIC. PUBLIC SECTION. INTERFACES zif_user_dao. METHODS preload IMPORTING it_users TYPE STANDARD TABLE. PRIVATE SECTION. DATA mt_users TYPE SORTED TABLE OF zty_user WITH UNIQUE KEY id.ENDCLASS.
CLASS zcl_user_dao_in_memory IMPLEMENTATION. METHOD preload. mt_users = CORRESPONDING #( it_users ). ENDMETHOD.
METHOD zif_user_dao~get_by_id. READ TABLE mt_users WITH TABLE KEY id = iv_id INTO rs_user. IF sy-subrc <> 0. RAISE EXCEPTION NEW zcx_user_not_found( iv_id = iv_id ). ENDIF. ENDMETHOD.
" ... інші методи аналогічноENDCLASS.Бізнес-логіка — знає тільки інтерфейс
Section titled “Бізнес-логіка — знає тільки інтерфейс”CLASS zcl_user_service DEFINITION PUBLIC FINAL CREATE PUBLIC. PUBLIC SECTION. METHODS: constructor IMPORTING io_dao TYPE REF TO zif_user_dao, activate IMPORTING iv_id TYPE zty_user_id RAISING zcx_user_not_found. PRIVATE SECTION. DATA mo_dao TYPE REF TO zif_user_dao.ENDCLASS.
CLASS zcl_user_service IMPLEMENTATION. METHOD constructor. mo_dao = io_dao. ENDMETHOD. METHOD activate. DATA(ls_user) = mo_dao->get_by_id( iv_id ). ls_user-is_active = abap_true. mo_dao->save( ls_user ). ENDMETHOD.ENDCLASS.У production:
DATA(lo_service) = NEW zcl_user_service( NEW zcl_user_dao_db( ) ).У тесті:
DATA(lo_fake_dao) = NEW zcl_user_dao_in_memory( ).lo_fake_dao->preload( VALUE #( ( id = '1' email = 'a@b' is_active = abap_false ) ) ).
DATA(lo_service) = NEW zcl_user_service( lo_fake_dao ).lo_service->activate( '1' )." перевіряємо стан через lo_fake_daoТест запускається за 30ms, не потребує БД, не залишає сліду.
DAO vs Repository
Section titled “DAO vs Repository”У DDD є близький патерн Repository: відмінність тонка.
| DAO | Repository | |
|---|---|---|
| Рівень | Технічний — ближче до SQL | Доменний — ближче до бізнес-мови |
| Повертає | Записи, близькі до таблиць | Aggregate roots (domain entities) |
| Орієнтація | CRUD | Specifications, query objects |
| Використання у SAP | Поширене і природне | Рідше, зазвичай поверх DAO |
У класичному Z-коді зазвичай — DAO. Repository має сенс у RAP-застосуваннях, де домен виражений явно.
SAP-специфіка
Section titled “SAP-специфіка”cds_view-і з асоціаціями — близькі до Repository у DDD-стилі: запити виразні, агрегати явні.- BOPF / RAP — стандартизовані доменні моделі, мають вбудований шар доступу, фактично закриваючи DAO.
PERSISTENT CLASSES(tx SE24, tx SE11 → viewSCMG_) — SAP-old-school ORM, рідко використовується.cl_abap_dbfeatures— check для конкретної поведінки БД (HANA vs old DB) — у DAO це точка адаптації.
Підводні камені
Section titled “Підводні камені”- N+1 запитів. Класичний анти-патерн DAO:
LOOP AT orders. lo_user_dao->get_by_id( order-user_id ).— на 1000 замовлень буде 1000 SELECT-ів. Додай методget_many( it_ids )і викликай його один раз. - Кеш. Якщо DAO часто читає одне — загортай у caching proxy, а не пиши кеш всередині DAO. Розділення відповідальності.
- Транзакції. DAO не керує комітами — це задача LUW-оркестратора.
COMMIT WORK/ROLLBACK WORKв DAO — антипатерн. DAO тільки робитьMODIFY/UPDATE. Див. SAP LUW. - Select на join-ах. Якщо запит склеює 4 таблиці — це НЕ обовʼязково зло, але подумай: може бути окремий
dao_order_with_customer_and_partner. Тонкі DAO по одній таблиці — не догма, коли join у БД ефективніший. - Чи створювати DAO для довідників? Для T***, TVARVC — так, якщо використовується у кількох місцях. Для одноразового
SELECTу репорті — overkill.
Коли НЕ використовувати
Section titled “Коли НЕ використовувати”- Одне читання в одному місці —
SELECTinline. - RAP-застосування — там DAO-шар уже реалізовано фреймворком.
- Read-only звіт з одним-двома SELECT-ами у model-класі — він сам по собі вже частковий DAO.