Реляционные запросы не нарушают инкапсуляцию в Udb

  !   Данная информация предназначена только только для IT-специалистов по системной интеграции модулей БИОСОФТ-М. (см. Руководства пользователя к программным продуктам)
Проблема

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

Как бы должно быть

  • либо медленно иобъектно-ориентированоинкапсулировано,
  • либо реляционнои эффективно но все в одной куче неразвиваемой, не модульной, общепубличной структуры БД.

Глядя на SQL кажется что оптимизированный реляционный запрос эффективно использует таблицы, смешивая отражаемые ими приватные проперти объектов в одном публичном выражении. А когда каждый запрос инкапсулируется классом и использует только локальные приватные идентификаторы таблиц и колонок то выходит много лишних запросов - не эффективно. Соответственно кажется что надо выбирать либо запрос на множестве JOIN эффективен но рушит инкапсуляцию, либо инкапсулированные запросы напрочь убивают быстродействие реляционной СУБД.

Нет проблемы

На самом деле делема вполне разрешима. В Udb с использованием усовершенствованных итераторов Islib инкапсулированные друг от друга модули могут совместными усилиями формировать реляционные запросы со своими приватными таблицами. При этом приватность реализации каждого модуля никак не страдает. Модулям не надо знать ничего о структуре БД друг друга, о связях таблиц и именах колонок.

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

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

Никаких волшебных хаков для этого библиотеки не предусматривают. Объединение таблиц приватных друг от друга БД использует обычный механизм динамического построения запросов Qx и вызвовы обычных интерфейсных функций для общения между участвующими хранилищами.

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

Клиент обращается к итератору одного из хранилищ (будем называть его "первичным") а приватная реализация этого итератора обращается к публичным интерфейсам других хранилищ ("вторичных") с просьбой дополнить формируемый SQL запрос необходимыми конструкциями для получения требуемых объектов. Вторичные хранилища добавляют к запросу нужные JOIN своих таблиц и возвращают нужные критерии фильтрации.

В этой процедуре модули обмениваются между собой абстрактным инкапсулятором динамического SQL запроса к которому можно что то осознанно добавить но нельзя получить никакой приватной информации добавленной к этому запросу другими модулями.

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

При этом не возникает необходимости передавать между модулями ссылок на приватные проперти или имен конкретных таблиц. Что делает возможным независимое и приватное эволюционирование структуры каждой локальной БД.

Пример

Демонстрационный пример приведен в Absform на примере двух инкапсулированных БД - одна для хранения протокола посетителей условного вебсайта (пакет Relog), другая для идентификации и классификации разных видов посетителей (пакет Relid).

Обе БД независимы. Ничего не знают о структуре БД друг друга. В том числе могут иметь разные реализации.

Запрос гипотетического клиента направленный в хранилище Relog с просьбой выдать список посетителей отклассифицированных согласно БД Relid оформлен итератором в Relog.

Реализация этого итератора обращается к публичному интерфейсу Relid с просьбой подготовить необходимые таблицы для классификации пользователей. Relog ничего не знает даже одна там таблица, или много объединенных JOIN или кеши какие то. Со своей стороны Relog добавляет к запросу свои таблицы с логами посетителей и регистрационными данными пользователей о которых ничего не нужно знать Relid. Relog предоставляет параметры посетителя в виде абстрактных Qx объектов динамического SQL а Relid симметричные же абстракции классификационных фильтров. В данном простом примере через LIKE бровзер из лога посетителей сравнивается с паттернами известных БД User Agentов. Здесь даже знание оператора для JOIN ON нужно инкапсулировать. Классификационная БД Relid знает что сопоставление просиходит через LIKE поэтому приватному инкапсулитору в Relid и должно делегироваться финальное формирование условия JOIN.

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

Все решение использует только

  • средства динамического формирования SQL запроса со статическим контролем его корректности, автоматическим безопасным и эффективным биндингом параметров между C++ и SQL
  • стандартные межмодульные интерфейсы Islib и привычные итераторы.
queryorcount CAbsformRelogStoreImpl::OnQueryVisitsInCategory(
        out iter& out_i,
        out ref<CAbsformRelogVisitIface>& out_rVisit,
        out ref<CAbsformRelidCategoryIface>& out_rCategory,
        key keyVisit,
        key keyRelidCategory)
{
    // part of the query is generated by another module
    ref<CAbsformRelidStoreIface> rAbsformRelidStore =
        x_rAbsformRelidStore;
    ref<CAbsformRelidQueryDetectCategoryIface>
        rDetectCategory;
 
    // insert query parts from other module
    rDetectCategory->
        InitQueryVisitsInCategory(
            out out_i,
            keyRelidCategory,
            rAbsformRelidStore);
 
    ref<CQueryIfaceGp> rQuery =
        out_i.GetNextQuery();
 
    from<CDbAbsformRelogVisit> fromVisit(rQuery);
 
    if (rQuery->Init())
    {
        // get a part of SQL from another module
        qxbool qxboolDetectCategoryByBrowser =
            rDetectCategory->
                PrepareQxDetectCategoryByBrowser(
                    (Qx
                        fromVisit->x_sAbsformRelogVisitBrowser),
                    rAbsformRelidStore);
 
        // join visitor log table over Relid tables we don't see here
        fromVisit.
            Join(
                x_rAbsformRelogRoot->
                    _x_dblistAbsformRelogVisit,
                keyVisit,
                qxboolDetectCategoryByBrowser);
 
        // (see Join condition)
        rQuery->x_qxboolWhere =
            Qx
                true;
 
        rQuery->x_qxsortOrderBy =
            Qx
                fromVisit->x_sAbsformRelogVisitDate
                    ORDER(E_Order_Ascending);
    }
 
    if (rQuery->Next())
    {
        rDetectCategory->
            FetchResultCategory(
                out out_rCategory,
                rAbsformRelidStore);
 
        ref<CAbsformRelogVisitImpl> rAbsformRelogVisit;
        rAbsformRelogVisit->_x_pAbsformRelogStore = this;
        rAbsformRelogVisit->x_rDbAbsformRelogVisit = fromVisit.FetchRef();
        out_rVisit = rAbsformRelogVisit;
    }
 
    return rQuery;
}
void CAbsformRelidQueryDetectCategoryImpl::OnInitQueryVisitsInCategory(
        out iter& out_i,
        key keyOnlyInRelidCategory,
        ref<CAbsformRelidStoreIface> rAbsformRelidStore)
{
    ref<CAbsformRelidStoreImpl> rStore =
        rAbsformRelidStore;
 
    ref<CQueryIfaceGp> rQuery =
        out_i.GetNextQuery();
 
    from<CDbAbsformRelidCategory> fromCategory(rQuery);
    _m_fromCategory = fromCategory;
    from<CDbAbsformRelidDetect> fromDetect(rQuery);
    _m_fromDetect = fromDetect;
 
    if (rQuery->Init())
    {
        fromCategory.
            BeginFrom(
                rStore->
                    x_rAbsformRelidRoot->
                        _x_dblistAbsformRelidCategory,
                key());
 
        fromDetect.
            Join(
                rStore->
                    x_rAbsformRelidRoot->
                        _x_dblistAbsformRelidDetect,
                key(),
                Qx
                    fromDetect->x_keyAbsformRelidCategory ==
                        fromCategory->GetKey()
                    &&
                    fromDetect->x_keyAbsformRelidCategory ==
                        keyOnlyInRelidCategory);
 
        _m_qxstringDetectAgent =
            Qx
                fromDetect->x_sAbsformRelidAgentLike;
    }
}
qxbool CAbsformRelidQueryDetectCategoryImpl::OnPrepareQxDetectCategoryByBrowser(
        qxstring qxstringBrowserString,
        ref<CAbsformRelidStoreIface> rAbsformRelidStore)
{
    rAbsformRelidStore;
 
    return
        Qx
            (qxstringBrowserString)
                LIKE
                (_m_qxstringDetectAgent);
}
void CAbsformRelidQueryDetectCategoryImpl::OnFetchResultCategory(
        out ref<CAbsformRelidCategoryIface>& out_rCategory,
        ref<CAbsformRelidStoreIface> rAbsformRelidStore)
{
    ref<CAbsformRelidStoreImpl> rStore =
        rAbsformRelidStore;
 
    ref<CAbsformRelidCategoryImpl> rCategory;
 
    // return query result encapsulator
    rCategory->_x_pAbsformRelidStore = rStore;
    rCategory->x_rDbAbsformRelidCategory = _m_fromCategory.FetchRef();
 
    out_rCategory = rCategory;
}

Естественно все участвующие логические БД должны быть физически расположены в одной низкоуровневой БД (ATTACH DB?). Это в Udb хорошо отлажено, не проблема и не угроза для приватности этих БД.

Не злоупотреблять

Мельтешением изолированными БД не нужно злоупотреблять и множетво прикладных модулей может обслуживать одно хранилище тогда все будет еще проще. Такое хранилище будет через ООП интерфейсы предоставлять только тот доступ который необходим.