Как и большинство проектов с открытым исходным кодом, Celesta возникла как попытка решить практическую проблему, когда не было других доступных адекватных инструментов. В этом случае необходимо было быстро создавать и развертывать большое количество небольших веб-приложений на Java, которые работают с СУБД.
Конечно, существует множество фреймворков и библиотек для решения этой проблемы, но для нас использование любой из них привело к дорогостоящей и медленной разработке. Поддержание системы миграции баз данных и создание автоматизированной системы тестирования и развертывания было трудоемким. Существующие подходы, подходящие для создания больших приложений, оказались громоздкими, когда возникла необходимость массового производства относительно небольших сервисов с использованием Spring Boot. Нам также нужно было повторно использовать код, написанный для разных проектов, в том числе для разных типов РСУБД.
И Celesta, бесплатная библиотека с открытым исходным кодом, была создана, чтобы упростить эту задачу и помочь нам сосредоточиться на бизнес-логике.
Короче говоря, Селеста это:
- Промежуточный уровень между реляционной базой данных и кодом бизнес-логики, реализующий основанный на базе данных подход к проектированию.
- Средство для переноса структуры базы данных.
- Основа для тестирования кода, работающего с данными.
Celesta поддерживает следующие типы реляционных баз данных: PostgreSQL, MS SQL Server, Oracle, Firebird (бета) и H2.
Основные возможности Celesta:
- Идея очень похожа на основной принцип Java: «Пиши один раз, запускай на каждой поддерживаемой СУБД». Код бизнес-логики не знает, для какой базы данных он выполняется. Вы можете написать код бизнес-логики и запустить его на MS SQL Server, затем переключиться на PostgreSQL, и переход будет плавным. Ну, почти :).
- Автоматическая миграция живой базы данных. Большая часть жизненного цикла проекта Celesta происходит, когда база данных уже существует и заполняется данными для сохранения, и в то же время схема должна постоянно изменяться. Celesta может автоматически настроить структуру базы данных в соответствии с вашей схемой данных.
- Тестирование. Вы можете автоматически тестировать методы, изменяя данные в базе данных и делать это легко, быстро и аккуратно, не прибегая к внешним инструментам, таким как DbUnit и контейнеры.
Зачем вам нужен независимый код типа базы данных?
Не случайно мы впервые упомянули независимость кода бизнес-логики от типа СУБД — код Celesta не знает, на какой СУБД он выполняется. С чего бы это?
Прежде всего, выбор RDMBS часто является вопросом предпочтения. У наших клиентов часто есть предпочтительные RDMBS, в которые они инвестировали, и они хотят, чтобы решения разрабатывались для существующей инфраструктуры. Технологический ландшафт меняется: сегодня PostgreSQL становится все более частым, тогда как всего несколько лет назад мы увидели доминирование MS SQL Server. Celesta поддерживает наиболее широко используемые СУБД, и эти изменения нас не касаются.
Во-вторых, мы хотели повторно использовать код от проекта к проекту и создать библиотеку многократного использования. Такие вещи, как иерархические каталоги и модули доставки уведомлений по электронной почте, по сути являются стандартными, так зачем поддерживать несколько версий для клиентов с разными RDMBS?
И последнее, но не менее важное: он позволяет пользователям запускать модульные тесты без таких инструментов, как DbUnit и контейнеров, использующих встроенную базу данных H2. База данных H2 не требует установки и запускается мгновенно в режиме в памяти. Celesta быстро создает схему данных для запуска тестов и «забывания» базы данных впоследствии.
При независимости от типа базы данных на самом деле не имеет значения, для какой базы данных выполняется бизнес-логика, поэтому, если в H2 не возникает ошибок, то же самое будет и в PostgreSQL. Конечно, команде разработчиков Celesta необходимо выполнить несколько тестов на реальной СУБД, чтобы убедиться, что платформа одинаково выполняет свой API на разных RDMBS. И мы делаем это, поэтому разработчики бизнес-логики избавлены от этой задачи.
CelestaSQL
Как достигается эта функциональность между базами данных? Естественно, цена этого — необходимость в специальном API, чтобы изолировать бизнес-логику от любых аспектов, связанных с базой данных, для работы с данными. Celesta генерирует код доступа к данным Java-классов, а для базы данных — SQL-код и некоторые вспомогательные объекты.
Celesta не допускает объектно-реляционного отображения, как есть, потому что дизайн модели данных основан на структуре базы данных, а не на классах. Т.е. сначала разрабатывается модель ER для таблиц, а затем Celesta генерирует классы доступа к данным (курсоры) в соответствии с ней.
Можно добиться одинакового поведения кода, работающего со всеми поддерживаемыми СУБД, только для функций, реализованных более или менее одинаково в каждой из них. Если мы используем диаграмму Эйлера для представления наборов функций поддерживаемых баз данных, мы получим следующий результат:
Если мы обеспечим полную независимость от типа базы данных, функциональные возможности пользователей Celesta должны лежать на пересечении всех наборов функций. И на первый взгляд это кажется серьезным ограничением. Конечно, некоторые специфические особенности, скажем, MS SQL Server, для нас недоступны. Но все реляционные базы данных поддерживают таблицы, внешние ключи, представления, последовательности и SQL-запросы с операторами JOIN и GROUP BY. Это инструменты, которые мы можем предоставить разработчикам. Мы предлагаем «обобщенный SQL», который передается в запросы SQL для соответствующих диалектов базы данных, и называем его CelestaSQL.
Язык CelestaSQL включает DDL для определения объектов базы данных и запросы SELECT для представлений и фильтров, но он не содержит команд DML — изменение выполняется курсорами, и мы рассмотрим их позже.
Каждая база данных имеет свой собственный набор типов данных. И у языка CelestaSQL тоже есть свой. В то время эта статья была написана, есть девять из них, и в этой таблице , они сравниваются с типами из различных баз данных и типов данных Java.
Девять типов могут показаться немногочисленными (по сравнению с числом, поддерживаемым , например, PostgreSQL ), но в действительности их достаточно для хранения финансовой, торговой и логистической информации: строки, целые числа и значения с плавающей запятой, даты, логические значения, и BLOB всегда достаточно для представления такого рода данных.
Язык CelestaSQL описан в документации, содержащей множество синтаксических диаграмм.
Модификация структуры базы данных: идемпотентный DDL
Еще одной ключевой функциональной возможностью Celesta является подход к миграции структуры производственной базы данных по мере развития проекта. Эта функция реализована с использованием встроенного подхода с использованием идемпотентного DDL.
Короче говоря, когда мы пишем следующий текст в CelestaSQL:
SQL
1
CREATE TABLE OrderLine(
2
order_id VARCHAR(30) NOT NULL,
3
line_no INT NOT NULL,
4
item_id VARCHAR(30) NOT NULL,
5
item_name VARCHAR(100),
6
qty INT NOT NULL DEFAULT 0,
7
cost REAL NOT NULL DEFAULT 0.0,
8
CONSTRAINT Idx_OrderLine PRIMARY KEY (order_id, line_no)
9
);
Celesta интерпретирует это не как «создать таблицу и, если она уже существует, вернуть ошибку», а как «изменить таблицу, чтобы она соответствовала требуемой структуре». Другими словами: «Если таблица не существует, создайте ее, и если она существует, посмотрите на ее поля, их типы, индексы, внешние ключи, значения по умолчанию и т. Д. И измените их в случае необходимости что-либо изменить. соответствовать требуемой структуре. »
Этот подход позволяет осуществлять рефакторинг и контроль версий схемы базы данных с помощью сценариев определения структуры базы данных:
- Мы всегда видим текущую требуемую модель структуры (в отличие от таких инструментов, как Liquibase).
- Система контроля версий позволяет пользователям видеть, что изменилось в структуре с течением времени, кто внес изменения и почему.
- И когда дело доходит до
ALTER
команд, Селеста производит и выполняет их при необходимости под капотом.
Конечно, у этого подхода есть свои ограничения. Селеста прилагает большие усилия, чтобы выполнить автоматическую миграцию безболезненно и незаметно, но иногда это невозможно. Мы объяснили обоснование, возможности и ограничения этого подхода в этой статье .
Celesta сохраняет контрольные суммы сценариев DDL для ускорения проверки / обновления структуры базы данных (проверка структуры базы данных и запуск обновления только при изменении контрольной суммы). Проблемы, связанные с порядком изменения взаимозависимых объектов, устраняются благодаря топологической сортировке зависимостей между схемами и внешними ключами. Автоматическая миграция подробно описана в документации .
Может ли механизм переноса базы данных использовать идемпотентный DDL, который можно использовать отдельно от Celesta? Да, 2bass , инструмент, который использует Celesta внутри, делает это возможным.
Создание проекта Celesta и модели данных
Демо-проект, о котором мы поговорим, доступен на GitHub . Давайте поговорим о том, как использовать Celesta при написании приложения Spring Boot. Нам понадобятся следующие зависимости Maven:
-
org.springframework.boot:spring-boot-starter-web
иru.curs:spring-boot-starter-celesta
(см. документацию для более подробной информации). - Вы должны использовать,
ru.curs:celesta-system-services
если вы не используете Spring Boot. - Для генерации кода доступа к данным,
ru.curs:celesta-maven-plugin
требуется. См. Демонстрационные исходные файлы и документацию для деталей конфигурации. - Чтобы включить написание модульных тестов Junit5 для методов модификации данных, добавьте
ru.curs:celesta-unit
в область тестирования.
Теперь давайте создадим модель данных и сгенерируем классы доступа к данным.
Предположим, что это проект для компании электронной коммерции, недавно объединенной с другой компанией. И у каждого из них есть свои базы данных. Они получают заказы на покупку, но пока они не присоединяются к своим базам данных, им нужна единая точка входа для получения заказов извне.
Точку входа можно реализовать традиционным способом: HTTP-сервис с операциями CRUD, сохраняющий данные в реляционной базе данных.
Подход, основанный на базе данных, реализован в Celesta, поэтому, во-первых, нам нужно создать структуру таблиц. Порядок, как мы все знаем, является составной сущностью: он состоит из одного заголовка, содержащего информацию о клиенте, дате заказа и других атрибутах, и нескольких строк (элементов).
Итак, давайте начнем, создайте:
src/main/celestasql
Папка по умолчанию — это путь к CelestaSQL сценариям проекта.- Подпапка, соответствующая структуре папок пакета java (в данном случае
ru/curs/demo
). .sql
Файл в папке пакета со следующим содержанием:
SQL
xxxxxxxxxx
1
CREATE SCHEMA demo VERSION '1.0';
2
3
/**Order header*/
4
CREATE TABLE OrderHeader(
5
id VARCHAR(30) NOT NULL,
6
date DATETIME,
7
customer_id VARCHAR(30),
8
/**Customer name*/
9
customer_name VARCHAR(50),
10
manager_id VARCHAR(30),
11
CONSTRAINT Pk_OrderHeader PRIMARY KEY (id)
12
);
13
14
/**Order line*/
15
CREATE TABLE OrderLine(
16
order_id VARCHAR(30) NOT NULL,
17
line_no INT NOT NULL,
18
item_id VARCHAR(30) NOT NULL,
19
item_name VARCHAR(100),
20
qty INT NOT NULL DEFAULT 0,
21
cost REAL NOT NULL DEFAULT 0.0,
22
CONSTRAINT Idx_OrderLine PRIMARY KEY (order_id, line_no)
23
);
24
25
ALTER TABLE OrderLine ADD CONSTRAINT fk_OrderLine FOREIGN KEY (order_id) REFERENCES OrderHeader(id);
26
27
CREATE VIEW OrderedQty AS
28
SELECT item_id, sum(qty) AS qty FROM OrderLine GROUP BY item_id;
Мы определили две таблицы, связанные с внешним ключом и видом для возврата общего количества продуктов во всех заказах. Как вы можете видеть, это ничем не отличается от стандартного SQL. Тем не менее, есть некоторые конкретные аспекты. Например, все имена таблиц и полей, которые мы используем, должны быть преобразованы в имена классов Java и переменных. Поэтому мы не можем использовать пробелы и дефисы. Вы могли также заметить, что некоторые комментарии, которые мы добавили выше имен некоторых таблиц и полей, начинаются не с обычного /*
, а с /**
комментариев JavaDoc, и для этого есть причина! Комментарий для объекта, начинающегося с /**
, доступен во время выполнения в его .getCelestaDoc()
свойство. Это может помочь, когда мы хотим добавить дополнительную мета-информацию для элементов базы данных: например, понятные человеку имена полей, информацию о представлении полей в пользовательском интерфейсе и так далее.
Сценарий CelestaSQL служит двум одинаково важным целям: развертывать / изменять структуру реляционной базы данных и генерировать код класса доступа к данным.
Теперь мы можем генерировать классы доступа к данным, просто выполнив mvn generate-sources
команду, а если вы работаете в IDEA, — нажав кнопку «Создать источники и обновить папки» на панели управления Maven. В любом случае IDEA распознает созданную папку target/generated-sources/celesta
как созданную исходную папку. Сгенерированный код будет выглядеть следующим образом, один класс для каждого объекта в базе данных:
Подключение к базе данных устанавливается в настройках приложения, в нашем случае, в src/main/resources/application.yml
файле. При использовании spring-boot-starter-celesta
, завершение кода IDEA предложит доступные варианты .
Если мы не хотим возиться с запуском «настоящей» СУБД только для демонстрации, мы можем заставить Celesta работать со встроенной базой данных H2 в режиме в памяти, установив следующую конфигурацию:
YAML
xxxxxxxxxx
1
celesta
2
h2
3
inMemorytrue
Чтобы подключить «настоящую» базу данных, измените ее на что-то вроде этого:
YAML
xxxxxxxxxx
1
celesta:
2
jdbc:
3
url: jdbc:postgresql://127.0.0.1:5432/celesta
4
username: <your_username>
5
password: <your_password>
(в этом случае вам также необходимо добавить драйвер JDBC PostgreSQL в ваше приложение через зависимость Maven).
При запуске приложения Celesta, подключенного к серверу базы данных, вы видите, что необходимые таблицы, представления, индексы и т. Д. Создаются для пустой базы данных и обновляются до непустой, чтобы соответствовать определению, данному в сценариях Celesta SQL DDL.
Создание методов работы с данными
Разобравшись со структурой базы данных, вы можете начать писать бизнес-логику.
Для реализации управления разрешениями и регистрации изменений данных каждая операция с данными в Celesta выполняется от имени какого-либо пользователя — анонимные операции невозможны. Таким образом, весь код Celesta выполняется в контексте вызова, описанного в классе CallContext .
CallContext
активирует перед началом операций возможность изменения данных в базе данных.- В момент активации соединение с базой данных берется из пула, и транзакция начинается.
- После завершения операции
commit()
выполняется для случая,CallContext
если операция прошла успешно илиrollback()
во время операции возникнет необработанное исключение. - Наконец,
CallContext
закрывается, и соединение с базой данных возвращается в пул.
Если мы используем spring-boot-starter-celesta
, эти действия выполняются автоматически для всех методов, аннотируемых @CelestaTransaction
.
Например, мы хотим написать обработчик запроса, сохраняющий документ в базе данных. На уровне контроллера его код может выглядеть следующим образом:
Джава
xxxxxxxxxx
1
2
"/api") (
3
public class DocumentController {
4
private final DocumentService srv;
5
public DocumentController(DocumentService srv) {
6
this.srv = srv;
7
}
8
"/save") (
10
public void saveOrder( OrderDto order) {
11
CallContext ctx = new CallContext("user1"); //new SystemCallContext();
12
srv.postOrder(ctx, order);
13
}
14
}
Обычно на уровне метода контроллера (т.е. после аутентификации) мы знаем идентификатор пользователя и можем использовать его при создании CallContext
. Привязка пользователя к контексту определяет права доступа к таблицам и позволяет регистрировать изменения, сделанные от их имени. Если вы не хотите использовать систему управления разрешениями Celesta и предоставить полному праву контекста вызова все таблицы, вы можете SystemCallContext
вместо этого создать объект.
Способ сохранения заказа на уровне сервиса может выглядеть следующим образом:
Джава
xxxxxxxxxx
1
2
public class DocumentService {
3
4
public void postOrder(CallContext context, OrderDto doc) {
5
try (OrderHeaderCursor header = new OrderHeaderCursor(context);
6
OrderLineCursor line = new OrderLineCursor(context)) {
7
header.setId(doc.getId());
8
header.setDate(
9
Date.from(doc.getDate().atStartOfDay(
10
ZoneId.systemDefault()).toInstant()));
11
header.setCustomer_id(doc.getCustomerId());
12
header.setCustomer_name(doc.getCustomerName());
13
header.insert();
14
int lineNo = 0;
15
for (OrderLineDto docLine : doc.getLines()) {
16
lineNo++;
17
line.setLine_no(lineNo);
18
line.setOrder_id(doc.getId());
19
line.setItem_id(docLine.getItemId());
20
line.setQty(docLine.getQty());
21
line.insert();
22
}
23
}
24
}
Обратите внимание на @CelestaTransaction
аннотацию. Это позволяет DocumentService
прокси-объекту выполнять все сервисные действия с CallContext ctx
параметром, описанным выше. Итак, в начале выполнения метода он уже связан с подключением к базе данных, и транзакция готова к запуску. Между тем, мы можем сосредоточиться на написании бизнес-логики (в данном случае, на чтении OrderDto
объекта и его сохранении в базе данных).
Для этого мы используем так называемые курсоры, которые создаются классами, используя celesta-maven-plugin
. Мы уже видели, как они выглядят. Для каждого объекта схемы - в данном случае две таблицы и одно представление - создается класс. И теперь мы можем использовать эти классы для доступа к объектам базы данных в нашей бизнес-логике.
Вам нужен следующий код, чтобы создать курсор для таблицы заказов и выбрать первую запись:
Джава
xxxxxxxxxx
1
OrderHeaderCursor header = new OrderHeaderCursor(context);
2
header.tryFirst();
После создания header
объекта мы можем получить доступ к полям записи таблицы, используя методы получения и установки:
При создании активного курсора мы должны использовать активный контекст вызова - это единственный способ создать его. Контекст вызова хранит информацию о текущем пользователе и его правах доступа.
Мы можем многое сделать с объектом курсора: фильтровать, перемещаться, вставлять, удалять и изменять записи. См. Документацию для подробного описания API курсора.
Например, мы можем разработать наш демонстрационный код следующим образом:
Джава
xxxxxxxxxx
1
OrderHeaderCursor header = new OrderHeaderCursor(context);
2
header.setRange("manager_id", "manager1");
3
header.tryFirst();
4
header.setCounter(header.getCounter() + 1);
5
header.update();
Здесь мы устанавливаем фильтр для manager_id
поля и затем находим первую запись с tryFirst
методом.
В момент tryFirst
, когда поля курсора заполнены данными одной записи, мы можем их прочитать и присвоить. И когда данные в курсоре полностью подготовлены, выполнить, update()
чтобы сохранить содержимое курсора в базе данных.
С какими проблемами может столкнуться этот код? Конечно, он может столкнуться с состоянием гонки / потерянным обновлением! Это может произойти, потому что кто-то другой может получить доступ, изменить или обновить данные в базе данных в промежутке между моментом, когда мы получаем данные, tryFirst
и моментом, когда мы пытаемся обновить данные на этапе обновления.
После того, как данные прочитаны, курсор никак не мешает другим пользователям использовать их! Чтобы предотвратить потерянные обновления, Celesta использует оптимистическую блокировку. По умолчанию он создает recversion
поле в каждой таблице, увеличивает номер версии в ON UPDATE
триггере, проверяет, что обновленные данные имеют ту же версию, что и в таблице, и выдает исключение в случае несоответствия. Для получения дополнительной информации см. Раздел « Предотвращение потерянных обновлений » в документации.
Опять же, транзакция связана с CallContext
объектом. Если процедура Celesta завершается успешно, она фиксирует изменения и откатывается, если метод завершается с неперехваченным исключением. Таким образом, если в сложной процедуре возникает ошибка, вся транзакция, связанная с контекстом вызова, откатывается - все остается таким же, каким оно было до начала, и данные не повреждаются. Если по какой-то причине вам необходимо выполнить коммит в середине - например, в течение долгого времени при выполнении процедуры - вы можете сделать это, зафиксировав явным образом путем вызова context.commit()
.
Курсоры позволяют писать код, который эффективно работает с базой данных. Смотрите Best Practices раздел в документации Celesta , чтобы узнать , как сделать это правильно и избежать излишней нагрузки на базу данных.
Методы тестирования работы с данными
Давайте создадим модульный тест, чтобы проверить правильность работы сервисного метода, используемого для сохранения OrderDto
в базе данных.
Это легко сделать с помощью JUnit5 и расширения для него в celesta-unit
модуле. Структура теста следующая:
Джава
xxxxxxxxxx
1
2
public class DocumentServiceTest {
3
DocumentService srv = new DocumentService();
4
6
void documentIsPutToDb(CallContext context) {
7
OrderDto doc =...
8
srv.postOrder(context, doc);
9
//Check the fact that records are in the database
11
OrderHeaderCursor header = new OrderHeaderCursor(context);
12
header.tryFirst();
13
assertEquals(doc.getId(), header.getId());
14
OrderLineCursor line = new OrderLineCursor(context);
15
line.setRange("order_id", doc.getId());
16
assertEquals(2, line.count());
17
}
18
}
@CelestaTest
Аннотаций позволяет объявить CallContext context
параметр в тестировании методов. Этот контекст уже активирован и привязан к базе данных (в памяти H2), поэтому нам не нужно использовать прокси для класса обслуживания. Мы создаем это new
здесь, а не используя Spring. В то же время, нет никаких препятствий для использования средств тестирования Spring.
Мы создаем модульные тесты, предполагая, что база данных на момент их выполнения полностью пуста, но имеет структуру, которая нам нужна. После их выполнения нам не нужно беспокоиться об очистке базы данных. Эти тесты очень быстрые.
Давайте создадим вторую процедуру для возврата JSON с агрегированными значениями, показывающими количество заказанных продуктов.
Тест добавляет два заказа в базу данных, а затем проверяет агрегированное значение, возвращенное новым getAggregateReport
методом:
Джава
xxxxxxxxxx
1
2
void reportReturnsAggregatedQuantities(CallContext context) {
3
srv.postOrder(context, . . .);
4
srv.postOrder(context, . . .);
5
Map<String, Integer> result = srv.getAggregateReport(context);
6
assertEquals(5, result.get("A").intValue());
7
assertEquals(7, result.get("B").intValue());
8
}
Для реализации getAggregateReport
метода мы используем OrderedQty
представление, которое в файле CelestaSQL выглядит следующим образом:
SQL
xxxxxxxxxx
1
create view OrderedQty as
2
select item_id, sum(qty) as qty from OrderLine group by item_id;
Запрос стандартный: мы добавляем строки заказа по количеству и группируем по коду продукта. Чтобы получить доступ к его данным, мы можем использовать сгенерированный OrderedQtyCursor
класс. Мы объявляем курсор, повторяем его и собираем Map<String, Integer>
следующее:
Джава
xxxxxxxxxx
1
2
public Map<String, Integer> getAggregateReport(CallContext context) {
3
Map<String, Integer> result = new HashMap<>();
4
try (OrderedQtyCursor ordered_qty = new OrderedQtyCursor(context)) {
5
for (OrderedQtyCursor line : ordered_qty) {
6
result.put(ordered_qty.getItem_id(), ordered_qty.getQty());
7
}
8
}
9
return result;
10
}
Материализованные виды Celesta
Что не так с использованием представлений для получения агрегированных данных? Этот метод является действенным, но это аварийное ожидание, потому что запрос SQL выполняется все медленнее, поскольку система накапливает данные. Он должен складывать и группировать растущее количество строк таблицы. Как вы этого избегаете?
В MS SQL Server есть концепция материализованных (индексированных) представлений, которые хранятся в виде таблиц и быстро обновляются по мере изменения данных в исходных таблицах. Если бы мы работали в «ванильном» MS SQL Server, замена представлений на индексированные представления была бы именно тем, что нам нужно: извлечение агрегированного отчета не замедлится по мере накопления данных, и отчет будет обновлен в тот момент, когда элементы заказа поступят таблица, которая также будет оставаться примерно с той же скоростью, что и число строк.
Но каковы наши варианты с PostgreSQL в Celesta? Переопределите представление, добавив слово «материализованный»:
SQL
xxxxxxxxxx
1
create materialized view OrderedQty as
2
select item_id, sum(qty) as qty from OrderLine group by item_id;
Запустите систему и посмотрите, что случилось с базой данных.
Обратите внимание, что OrderedQty
представление исчезло и было заменено OrderedQty
таблицей. И как OrderLine
таблицы заполнены данными, так и информация в OrderedQty
таблице «волшебным образом» обновляется так же, как обновляются представления.
Здесь нет никакой магии: посмотрите на триггеры, определенные для OrderLine
таблицы. Получив задачу создания материализованного представления, Celesta проанализировала запрос и создала триггеры в OrderLine
таблице для обновления OrderedQty
. Добавив одно ключевое слово - materialized
- в файл CelestaSQL, мы решили проблему снижения производительности без единого изменения бизнес-логики!
Естественно, этот подход имеет довольно строгие ограничения. В Celesta вы можете только материализовать представления, построенные на одной таблице, без JOIN
s или агрегации GROUP BY
. Но этого достаточно, чтобы подготовить отчеты по счетам и запасам и аналогичные общие отчеты.
Заключение
Мы кратко объяснили основные возможности Celesta. Вы можете проверить GitHub и документацию, если вы заинтересованы в этой технологии.