Иерархия виртуальной модели данных CDS View

В настоящее время все большее распространение получают системы на основе S/4, одной из ключевых особенностей которых является основанная на CDS View виртуальная модель данных, на которой построена концепция “Code Push Down”.

Концепция виртуальных моделей заключается в том, что поверх таблиц БД строится древовидная иерархия CDS View, описывающих модели данных различного свойства с различными назначениями. В этой заметке я скажу пару слов про то, какие виды CDS View бывают в иерархии, и как правильно такие иерархии строить.

Виртуальная модель данных

Ключевой фичей S/4HANA систем является концепция “Code Push Down” - перенос вычисления модели данных с сервера приложений на сервер базы данных, а именно SAP HANA. При этом все построение модели, включающее расчеты, агрегации и фильтрации, происходят внутри БД, и через интерфейс БД на сервер приложений передается только тот набор данных, который требуется исполняемому приложению. Это противоположно подходу, реализованного в R3 системах, когда данные выбирались из базы данных максимально оптимальным способом и затем уже обрабатывались и фильтровались на сервере приложений (в ABAP коде).

Code Push Down (с) SAP AG

Картинка принадлежит SAP AG

Это достигается (по словам SAP) за счет того, что in-memory СУБД HANA позволяет произвести такие операции очень быстро (по сравнению с классическими РСУБД) и тем самым разгружает канал соединения между сервером приложений и сервером БД, и экономит время на обработку данных программой.

Основой для моделирования на HANA является виртуальная модель данных (Virtual Data Model - VDM), состоящая из набора CDS View различного характера.

CDS View логически разделяются на три уровня:

  • basic,
  • composite,
  • consumption.

Каждый из этих уровней определяется аннотацией @VDM.viewType: #<...> и соответственно BASIC/COMPOSITE/CONSUMPTION. В справке написано, что эта аннотация используется для внутреннего саповского структурирования и интерпретации CDS View, однако никакого реального эффекта от этих аннотаций не замечено, и явной информации о том, для чего конкретно они нужны, у меня нет. Если вы что-то по этому поводу знаете, поделитесь в комментариях.

Итак, рассмотрим подробнее каждый уровень VDM и примеры использования.

Basic View

В целях совместимости данные в S/4 в большинстве своем хранятся в тех же таблицах, что и в R3 (особенно старые продукты вроде ERP, так как новые вроде EHS реализованы сразу на BOPF). Кто работал со стандартными таблицами, знает, насколько они запутаны и насколько непрозрачно называются сами таблицы и их поля (к тому же большинство являются сокращениями от немецких названий).

Однако одной из целей переработки S/4 систем было упрощение транзакций и моделей данных. Поэтому поверх стандартных таблиц реализовываются Basic CDS View, которые имеют нормальные читабельные названия полей и самого ракурса вместо технических имен полей в таблице. Эти CDS View являются интерфейсом доступа к данным таблиц БД (и table functions), и прямое обращение к таблицам теперь считается плохой практикой.

В качестве примера можно посмотреть CDS View I_Material, реализующий интерфейсный доступ к данным таблицы мастерданных материалов MARA.

Предполагается, что для каждой таблицы будет создана только одна Basic View, описывающая эту конкретную атомарную сущность. Эта View должна быть максимально универсальной с целью ее переиспользования, то есть в ней не должно быть никаких не специфичных для нижележащей сущности данных - не должно быть агрегаций, изменений или конвертаций данных, параметров или WHERE-условий.

Basic View обычно считывают данные с одной таблицы БД, либо, если поля сущности распределены по нескольким таблицам, то допустимо делать JOIN с ними (хороший пример - CDS View I_SalesDocumentItem). Однако, чтобы описать связь с другими сущностями (другими таблицами), необходимо добавлять ассоциации на лежащие над ними Basic View, а не на сами таблицы.

Basic View должны иметь ассоциации только на другие Basic View. Эти ракурсы составляют нижний уровень иерархии VDM.

Для именования Basic CDS View принят нейминг I_*, или Z*_I_* соответственно.

Для определения ракурса используется аннотация @VDM.viewType: #BASIC.

По большому счету для каждой таблицы, которую вы создаете, должна создаваться Basic CDS View поверх нее, в которой будут описаны нормальные названия полей, их семантика, текстовые ассоциации и ассоциации к другим сущностям (также Basic CDS View ).

Composite View

Следующий уровень иерархии составляют Composite View - ракурсы, описывающие специфичные для определенного приложения или логики. Они описывают уже конкретную модель данных, со специфичной для нее логикой, фильтрацией, агрегацией, параметрами и т.п. Composite View получают данные из Basic View, или же из других композитных ракурсов. То же и с ассоциациями. Таким образом эти ракурсы комбинируют данные из базовых атомарных сущностей в конкретную модель, специфичную для одного или нескольких приложений.

По своей природе Composite View избыточны, то есть одни и те же базовые сущности могут встречаться в различных композитных ракурсах, например для агрегаций, аналитики, каких-то расчетов и конвертаций, просто выборки данных и так далее. Да и просто для различных приложений, где структура данных модели различна.

Однако они, как и базовые ракурсы, также могут быть переиспользованы, если построенная модель имеет универсальный характер - например CDS, рассчитывающая годовой оборот предприятия, может быть использована во множестве отчетов.

Для именования Composite View также принят нейминг I_*, или Z*_I_* соответственно.

Для определения ракурса используется аннотация @VDM.viewType: #COMPOSITE.

В целом Composite View представляет собой выборку данных из одной или множества базовых таблиц, скомпонованную и преобразованную таким образом, чтобы служить в качестве готовой модели данных для определенного приложения или вышестоящих CDS View.

Consumption View

Последний, верхний уровень иерархии - Consumption CDS View. Они описывают непосредственно взаимодействие приложения с нижележащей иерархией CDS, и создаются отдельно для каждого приложения. Они не являются универсальными, и не предполагают переиспользования (хотя, конечно, вам никто не мешает).

В Consumption View описываются специфические для приложения параметры - внешний вид полей Fiori приложения, средства поиска и другие характеристики относительно того, в каком виде данные будут потребляться приложением.

Для именования Consumption View принят следующий нейминг:

  • CDS для использования в приложениях имеют префикс C_*, или Z*_C_* соответственно;
  • CDS для использования во внешних API имеют префикс A_*, или Z*_A_* соответственно

Для определения ракурса используется аннотация @VDM.viewType: #CONSUMPTION.

В итоге на этом уровне мы описываем то, как данная модель будет выглядеть в рамках использующего ее приложения.

Построение иерархии CDS View

Для большинства стандартных таблиц CDS View уже существуют, поэтому рассмотрим пример с Z-таблицами.

Допустим у вас есть таблицы, описывающие список магазинов и набор доступного товара на каждом из них, а также история продаж.

  • ztest_shop - список магазинов;
  • ztest_shop_t - соответствующая текстовая таблица;
  • ztest_shop_prod - список товаров, находящихся в каждом магазине;
  • ztest_shop_hist - история продаж магазина.

Полная структура таблиц

Уровень базовых моделей

Сначала создаем для каждой таблицы Basic CDS View, в которых даем читаемые названия полям таблицы (принято использовать CamelCase, хотя по факту CDS View регистронезависимы). Описываем ассоциации другим Basic CDS View, например на заголовок и позиции документа соответственно, а также на CDS View для соответствующих тестовых таблиц.

Четырем таблицам БД соответствуют четыре Basic CDS View:

  • Ztest_I_Shop
  • Ztest_I_ShopText
  • Ztest_I_ShopProducts
  • Ztest_I_ShopSalesHistory

Пример:

@AbapCatalog.sqlViewName: 'ZTEST_VSHOP'
@AbapCatalog.compiler.compareFilter: true
@AccessControl.authorizationCheck: #CHECK
@VDM.viewType: #BASIC
@EndUserText.label: 'Shop Info'

define view Ztest_I_Shop
  as select from ztest_shop

  association [*] to Ztest_I_ShopText         as _Text         on _Text.ShopID = $projection.ShopID
  association [*] to Ztest_I_ShopProducts     as _Products     on _Products.ShopID = $projection.ShopID
  association [*] to Ztest_I_ShopSalesHistory as _SalesHistory on _SalesHistory.ShopID = $projection.ShopID
{
      @ObjectModel.text.association: '_Text'
      @ObjectModel.foreignKey.association:[ '_Products', '_SalesHistory']
  key shop_id as ShopID,
      @Semantics.address.label: true
      address as Address,

      _Text,
      _Products,
      _SalesHistory
}

Полный текст Basic CDS View

Эти CDS View описывают интерфейс доступа к данным соответствующих таблиц, и в дальнейшем будут они использоваться, когда различные приложения будут обращаться к данным и структуре этих таблиц в своих моделях.

Уровень моделей приложений

Далее переходим на уровень композитных ракурсов, на котором описаны специфичные для приложений модели. Допустим, что у нас есть приложение, которое будет показывать общее количество товара по всем магазинам и рассчитывать примерный срок пополнения запаса товара в каждом в зависимости от истории продаж.

На этом уровне реализуются различные операции, выполняемые над данными таблиц. А именно, агрегации, арифметические операции, конвертации валют, единиц измерения или дат, фильтрация данных и так далее. Само собой, в зависимости от сложности моделируемой логики, модель может состоять из множества CDS View, последовательно преобразующих данные. На практике такое часто случается, поэтому необходимо следить за сложностью иерархии и количеством таблиц, участвующих в построении модели.

В примере логика достаточно простая и модель приложения можно реализовать в несколько ракурсов.

Расчет даты, до которой необходимо пополнить запас товара в магазине:

@AbapCatalog.sqlViewName: 'ZTEST_VPRODREPL'
@AbapCatalog.compiler.compareFilter: true
@AccessControl.authorizationCheck: #CHECK
@VDM.viewType: #COMPOSITE
@EndUserText.label: 'Replenishment of each product'

define view Ztest_I_ProductReplenishment
  with parameters
    p_horizon_days : abap.int4,
    p_current_date : abap.dats
  as select from    Ztest_I_ShopProducts as Stock
    left outer join Ztest_I_ProductHorizonSum(
                    p_horizon_days: $parameters.p_horizon_days
                    )                    as History on  History.Material = Stock.Material
                                                    and History.ShopID   = Stock.ShopID
{
  key Stock.Material,
  key Stock.ShopID,

      @Semantics.businessDate.to: true
      cast(case
        when History.Amount is not null and History.Amount <> 0
          then dats_add_days(
            $parameters.p_current_date,
            ceil(division(Stock.AvailableAmount, History.Amount , 3) * $parameters.p_horizon_days),
            'FAIL')
          else '99991231'
      end as abap.dats) as ReplenishmentDate,
      
      _Shop,
      _Material
}

Полный текст Composite CDS View

Уровень отображений и интерфейсов

В последнюю очередь необходимо адаптировать эти модели данных для отображения. В случае smart templates это могут быть аннотации, описывающие отображение полей, в случае IDA это могут быть названия столбцов, также сюда относится OData exposure, создающая OData сервис для соответствующей CDS View. Также это могут быть какие-то дополнительные ассоциации (тексты, средства поиска, просто другие ракурсы), описание параметров полнотекстового поиска для различных полей, и т.п.

Выглядит это примерно так:

@AbapCatalog.sqlViewName: 'ZTEST_CPRODAMNT'
@AbapCatalog.compiler.compareFilter: true
@AccessControl.authorizationCheck: #CHECK
@VDM.viewType: #CONSUMPTION
@EndUserText.label: 'Available product amount'

define view Ztest_C_ProductAmount
  as select from Ztest_I_ProductAmount

  association [*] to Ztest_C_ProductReplenishment as _Replenishment on _Replenishment.Material = $projection.Material
{
  key Material,
      @EndUserText: {
        label: 'Available Amount',
        quickInfo: 'Amount available across all shops'
      }
      AvailableAmount,

      _Replenishment,
      _Material
}

Примеры двух получившихся Consumption CDS View

На основе данной модели уже можно строить приложение (это может быть сразу фронтенд в лице Fiori, либо какая-то прослойка в виде ABAP кода, либо какое-то еще приложение поверх) или же она может являться моделью для какого-либо API, доступного через gateway (например, для аналитики или статистики). Важно то, что в верхнем уровне иерархии каждый ракурс заточен уже под конкретное его применение, поэтому в нем можно и описать тексты названий полей и заголовков (которые доступны для перевода через SE63), реализовать еще какие-то узкоспециализированные вещи, и так далее. По большому счету создаете сущность, использующую модель данных в CDS - создавайте под нее Consumption View, не стесняясь делать его абсолютно неуниверсальным.

Итого

Конечно, есть случаи, когда модель достаточно проста, и нет необходимости разделять, например, базовый и композиционный уровни, а можно передавать данные уже в таком виде, в котором они есть в таблице (добавив только Consumption View поверх существующей Basic View). Да и ограничения относительно того, где реализовывать какие преобразования, довольно размыта.

Основная идея состоит лишь в следующем:

  • basic view - универсальные, независимые, атомарные;
  • composite view - специфичные для приложений, но все еще переиспользуемые;
  • consumption view - специфичные для одного кейса использования модели, абсолютно непереиспользуемые.

Примерно так выглядит иерархия виртуальной модели данных в CDS. Есть еще другие виды ракурсов, как аналитические, текстовые, бизнес-объекты и т.д. Но это уже совсем другая история…

comments