Перейти до вмісту

DAO — Data Access Object

DAO (Data Access Object) — паттерн, що інкапсулює всю роботу з БД для конкретної сутності в один клас за інтерфейсом. Бізнес-логіка не знає, де зберігаються дані — в ABAP SQL, в AMDP, в REST-API чи в тестовій памʼяті. Вона просить DAO: get, save, delete.

Для нетривіального читання/запису в ентерпрайз-коді це обовʼязковий шар: не роби SELECT напряму з бізнес-класу.

  • Бізнес-логіка має тестуватись без БД — підсовуємо zcl_user_dao_in_memory замість zcl_user_dao_db.
  • Одну сутність читають з кількох місць — дублювання SELECT стає ризиком.
  • Доступ до сутності може змінитись (БД → REST, on-prem → cloud) — зміна локалізується в одному класі.
  • Треба централізувати перевірки повноважень, валідацію, кеш — все в DAO, не розмазано.
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).
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, не потребує БД, не залишає сліду.

У DDD є близький патерн Repository: відмінність тонка.

DAORepository
РівеньТехнічний — ближче до SQLДоменний — ближче до бізнес-мови
ПовертаєЗаписи, близькі до таблицьAggregate roots (domain entities)
ОрієнтаціяCRUDSpecifications, query objects
Використання у SAPПоширене і природнеРідше, зазвичай поверх DAO

У класичному Z-коді зазвичай — DAO. Repository має сенс у RAP-застосуваннях, де домен виражений явно.

  • cds_view-і з асоціаціями — близькі до Repository у DDD-стилі: запити виразні, агрегати явні.
  • BOPF / RAP — стандартизовані доменні моделі, мають вбудований шар доступу, фактично закриваючи DAO.
  • PERSISTENT CLASSES (tx SE24, tx SE11 → view SCMG_) — SAP-old-school ORM, рідко використовується.
  • cl_abap_dbfeatures — check для конкретної поведінки БД (HANA vs old DB) — у DAO це точка адаптації.
  • 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 “Коли НЕ використовувати”
  • Одне читання в одному місці — SELECT inline.
  • RAP-застосування — там DAO-шар уже реалізовано фреймворком.
  • Read-only звіт з одним-двома SELECT-ами у model-класі — він сам по собі вже частковий DAO.