Введение: когда документация по БД перестаёт быть мёртвым грузом

Представьте: вы заходите в репозиторий, открываете папку schemas и через пять минут понимаете, как устроена база во всём проекте, со всеми связями. Никаких устаревших диаграмм в Confluence, никаких гаданий по коду миграций. Схема базы данных становится частью кодовой базы — её можно версионировать, ревьюить, тестировать. Модель в формате ArchDB становится единым источником истины, из которого автоматически генерируются документация, DDL-скрипты и даже ORM-сущности. Звучит как мечта? Для нас с командой это стало реальностью, когда мы перешли на ArchDB.

А теперь вспомните свои «боли»:

  • Вы тратите два часа на code review, потому что миграцию в Liquibase написал стажёр и невозможно понять, что он хотел сделать.

  • Синхронизация ORM-сущностей (JPA/Hibernate) с реальной схемой превращается в ад, если у вас больше пятидесяти таблиц.

  • Каждое изменение модели запускает рутину: обновить диаграмму, поправить документацию, переписать DDL, не забыть про тестовые данные.

  • Одинаковые колонки created_atupdated_by кочуют из таблицы в таблицу через copy-paste, а когда приходит задача добавить ещё одно поле аудита, приходится лезть во все сущности.

Конечно, сегодня существуют инструменты, которые автоматизируют создание миграций или позволяют рисовать схемы в веб-интерфейсе. Но они решают лишь часть проблем. ArchDB предлагает принципиально иной подход: модульное, декларативное описание схемы как кода. Это не просто DSL, а своего рода рецепт, превращающий хаотичный процесс проектирования данных в управляемый и автоматизированный поток. Вы описываете «что», а не «как» — декларативность фиксирует желаемое состояние («замороженное» состояние), из которого инструменты могут сгенерировать всё необходимое: от DDL-скриптов до визуальных диаграмм.

Но главное — ArchDB позволяет кодифицировать лучшие практики и типовые требования. Вместо того чтобы раз за разом описывать в документации «во всех таблицах должны быть поля created_at и updated_at», вы создаёте шаблон Auditable, который затем просто подключаете к нужным таблицам. Этот шаблон живёт в общей библиотеке и автоматически распространяется на все команды проекта или компании. Требования перестают быть просто словами — они становятся исполняемым кодом.

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

Эта статья для тех, кто устал от рутины: Team Lead’ов, аналитиков, архитекторов данных и senior‑разработчиков, сотрудников сопровождения, которым надоело наступать на одни и те же грабли. Мы разберём, как ArchDB позволяет:

  • реально ускорить проектирование — на практике время согласования изменений в схеме из сотни таблиц сокращается с дней до нескольких часов;

  • исключить дублирование через механизм шаблонов (Template);

  • декомпозировать монолитную модель на логические модули (Package и Schema), которые можно разрабатывать и версионировать независимо.

Дальше — никакой абстрактной философии. Только код, примеры и конкретные приёмы, которые вы сможете применить уже сегодня.

Взгляд за горизонт

Бывает, что хочется заглянуть в конец книги и посмотреть, чем всё это кончится. Этот раздел именно на такой случай.

Core.adb
// ==============================================
// Файл: Core.adb
// Полная модель интернет-магазина на ArchDB
// ==============================================

Package com.example.ecommerce.Core

// ----- Проект -----
Project ECommercePlatform [
  database: "postgresql",
  version: 2.5.0,
  author: "E-Commerce Team",
  description: "Производственная модель данных интернет-магазина",
  default_schema: "public"
]

// ----- Глобальные метаданные файла -----
Metadata [
  author: "Архитектурная команда",
  version: 3.4.11,
  description: "Шаблоны и основные бизнес-сущности",
  license: "Internal use only"
]

export Core as "com.example.ecommerce.Core" version 3.4.0

// ----- Схема основных данных -----

// ----- Базовые шаблоны для повторного использования -----

Template IdentifiableUuid
  [note: "Шаблон для сущностей с UUID идентификатором"] {

  id uuid [pk, default: `gen_random_uuid()`, note: "Уникальный идентификатор"]
}

Template Auditable
  [note: "Шаблон с полями аудита (кто и когда создал/обновил)"] {

  created_at    timestamp [not_null, default: `now()`]
  created_by    uuid [note: "ID пользователя-создателя", ref: > User.id, nullable]
  updated_at    timestamp [default: `now()`]
  updated_by    uuid [note: "ID пользователя, обновившего запись", ref: > User.id, nullable]

  indexes {
    idx_created_at (created_at) [note: "Для фильтрации по дате создания"]
    idx_updated_at (updated_at) [note: "Для поиска недавно измененных"]
  }
}

Template SoftDeletable
  [note: "Шаблон для мягкого удаления"] {

  is_deleted    boolean [default: false, note: "Флаг удаления"]
  deleted_at    timestamp [note: "Когда удалено"]
  deleted_by    uuid [note: "Кто удалил", ref: > User.id]
}

Template BusinessEntity extends IdentifiableUuid, Auditable, SoftDeletable
  [note: "Составной базовый шаблон для бизнес-сущностей"] {

  version_no    integer [default: 1, note: "Версия для оптимистичной блокировки"]
  status        varchar(50) [default: 'active', note: "Статус активности"]
}

Table User extends BusinessEntity
  [note: "Пользовательская система"] {

  email           varchar(255) [unique, not_null, note: "Основной email"]
  username        varchar(100) [unique, note: "Публичное имя"]
  password_hash   text [not_null]
  phone           varchar(50)
  date_of_birth   date

  indexes {
    idx_user_email    (email)
    idx_user_phone    (phone) [unique, name: "uidx_user_phone"]
  }

  checks {
    `char_length(password_hash) >= 8` [name: "ck_password_length"]
  }
}

// ----- Логическая группировка для документации -----
Group Core
  [note: "Шаблоны и основные бизнес-сущности", color: #e8f5e8] {

  Core.IdentifiableUuid
  Core.Auditable
  Core.SoftDeletable
  Core.BusinessEntity
  Core.User
}
Product.adb
// ==============================================
// Файл: Product.adb
// Полная модель интернет-магазина на ArchDB
// ==============================================

Package com.example.ecommerce.Product

// ----- Проект -----
Project ECommercePlatform [
  database: "postgresql",
  version: 2.5.0,
  author: "E-Commerce Team",
  description: "Производственная модель данных интернет-магазина",
  default_schema: "public"
]

// ----- Глобальные метаданные файла -----
Metadata [
  author: "Архитектурная команда",
  version: 3.6.3,
  description: "Продукты и категории",
  license: "Internal use only"
]

import "com.example.ecommerce.Core.adb" version 3.4.0

export Product as "com.example.ecommerce.Product" version 3.6.0

// ----- Схема продуктов и категорий -----

Table Product extends Core.BusinessEntity
  [note: "Каталог товаров"] {

  sku                 varchar(50) [unique, not_null, note: "Артикул"]
  name                varchar(500) [not_null]
  description         text
  price               decimal(12, 2) [not_null]
  cost_price          decimal(12, 2) [note: "Себестоимость"]
  weight_kg           decimal(8, 3)
  is_available        boolean [default: true]
  stock_quantity      integer [not_null, default: 0]
  reserved_quantity   integer [not_null, default: 0, note: "Зарезервировано в незавершенных заказах"]
  available_quantity  integer [generated: `stock_quantity - reserved_quantity`]

  indexes {
    idx_product_sku     (sku)
    idx_product_name    (name)
    (name, is_available) [name: "idx_product_search"]
  }

  checks {
    `price >= 0` [name: "ck_positive_price"]
    `cost_price >= 0` [name: "ck_positive_cost"]
    `price >= cost_price` [name: "ck_profit_margin"]
    `reserved_quantity <= stock_quantity` [name: "ck_valid_reservation"]
    `reserved_quantity >= 0` [name: "ck_nonnegative_reserved"]
  }
}

Table Category extends Core.IdentifiableUuid
  [note: "Категория товаров"] {

  parent_id     uuid [note: "Для иерархии", ref: > Category.id, on_delete: cascade]
  name          varchar(500) [not_null]
  slug          varchar(500) [unique, note: "Для URL"]
  sort_order    integer [default: 0]
}

// Связь многие-ко-многим для категорий товаров (через Relation)
Relation ProductCategory
  [note: "Связь товаров с категориями"] {

  Product.id <-> Category.id
}

// ----- Логическая группировка для документации -----
Group Product
  [note: "Продукты и категории", color: #e8f5e8] {

  Product.Product
  Product.Category
}
Order.adb
// ==============================================
// Файл: Order.adb
// Полная модель интернет-магазина на ArchDB
// ==============================================

Package com.example.ecommerce.Order

// ----- Проект -----
Project ECommercePlatform [
  database: "postgresql",
  version: 2.5.0,
  author: "E-Commerce Team",
  description: "Производственная модель данных интернет-магазина",
  default_schema: "public"
]

// ----- Глобальные метаданные файла -----
Metadata [
  author: "Архитектурная команда",
  version: 3.7.8,
  description: "Заказы и позиции заказов",
  license: "Internal use only"
]

import "com.example.ecommerce.Core.adb" version 3.4.0
import "com.example.ecommerce.Product.adb" version 3.6.0

export Order as "com.example.ecommerce.Order" version 3.7.0

// ----- Схема заказов -----

Enum AddressType
  [note: "Тип адреса"] {

  shipping
  billing
  both
}

Table UserAddress extends Core.BusinessEntity
  [note: "Таблица адресов"] {

  user_id       uuid [not_null, ref: > Core.User.id, on_delete: cascade]
  address_type  AddressType [not_null]
  full_name     varchar(255) [not_null]
  street        text [not_null]
  city          varchar(100) [not_null]
  postal_code   varchar(20) [not_null]
  country_code  char(2) [not_null]
}

Enum OrderStatus
  [note: "Статусы заказа", color: #e3f2fd] {

  draft         [color: #bbdefb, note: "Черновик"]
  pending       [color: #90caf9, note: "Ожидает оплаты"]
  paid          [color: #64b5f6, note: "Оплачен"]
  processing    [color: #42a5f5, note: "В обработке"]
  shipped       [color: #2196f3, note: "Отправлен"]
  delivered     [color: #1e88e5, note: "Доставлен"]
  cancelled     [color: #ffcdd2, note: "Отменен"]
  refunded      [color: #ef9a9a, note: "Возвращен"]
}

Table Order extends Core.BusinessEntity
  [note: "Заказы"] {

  user_id                   uuid [not_null]
  order_number              varchar(50) [unique, not_null]
  total_amount              decimal(12, 2) [not_null]
  tax_amount                decimal(12, 2) [default: 0]
  discount_amount           decimal(12, 2) [default: 0]
  status                    OrderStatus [not_null, default: 'pending']
  notes                     text
  shipping_address_id       uuid [ref: > UserAddress.id]
  billing_address_id        uuid [ref: > UserAddress.id]
  shipping_cost             decimal(12,2) [default: 0]
  estimated_delivery_date   date

  indexes {
    idx_order_user    (user_id, created_at desc)
    idx_order_number  (order_number)
    idx_order_status  (status, created_at)
  }

  checks {
    `total_amount >= 0` [name: "ck_positive_total"]
    `tax_amount >= 0` [name: "ck_positive_tax"]
  }
}

Table OrderItem extends Core.IdentifiableUuid
  [note: "Позиции в заказе"] {

  order_id      uuid [not_null]
  product_id    uuid [not_null]
  quantity      integer [not_null]
  unit_price    decimal(12, 2) [not_null]
  subtotal      decimal(12, 2) [not_null, generated: `quantity * unit_price`]

  indexes {
    idx_order_item_order      (order_id)
    idx_order_item_product    (product_id)
    (order_id, product_id) [unique, name: "uidx_order_product"]
  }

  checks {
    `quantity > 0` [name: "ck_positive_quantity"]
    `unit_price >= 0` [name: "ck_positive_unit_price"]
  }
}

Ref: Order.user_id -> Core.User.id [on_delete: restrict, note: "Заказ принадлежит пользователю"]
Ref: OrderItem.order_id -> Order.id [on_delete: cascade, note: "Позиция принадлежит заказу"]
Ref: OrderItem.product_id -> Product.Product.id [on_delete: restrict, note: "Позиция ссылается на товар"]

// ----- Логическая группировка для документации -----
Group Order
  [note: "Заказы", color: #e8f5e8] {

  Order.AddressType
  Order.UserAddress
  Order.OrderStatus
  Order.Order
  Order.OrderItem
}
Finance.adb
// ==============================================
// Файл: Finance.adb
// Полная модель интернет-магазина на ArchDB
// ==============================================

Package com.example.ecommerce.Finance

// ----- Проект -----
Project ECommercePlatform [
  database: "postgresql",
  version: 2.5.0,
  author: "E-Commerce Team",
  description: "Производственная модель данных интернет-магазина",
  default_schema: "public"
]

// ----- Глобальные метаданные файла -----
Metadata [
  author: "Архитектурная команда",
  version: 3.3.17,
  description: "Финансовые операции и платежи",
  license: "Internal use only"
]

import "com.example.ecommerce.Core.adb" version 3.4.0
import "com.example.ecommerce.Order.adb" version 3.7.0

export Finance as "com.example.ecommerce.Finance" version 3.3.0

// ----- Схема финансов -----

Enum PaymentType
  [note: "Способ оплаты"] {

  credit_card
  debit_card
  paypal
  bank_transfer
  cash_on_delivery
}

Table Payment extends Core.BusinessEntity
  [note: "Платеж"] {

  order_id          uuid [not_null, ref: > Order.Order.id, on_delete: restrict, note: "Платеж для заказа"]
  amount            decimal(12, 2) [not_null]
  payment_type      PaymentType [not_null]
  transaction_id    varchar(100) [unique, note: "ID транзакции в платежной системе"]
  is_successful     boolean [default: false]
  processed_at      timestamp
  gateway_response  jsonb [note: "Ответ платежного шлюза для аудита"]
  refunded_amount   decimal(12,2) [default: 0]

  indexes {
    idx_payment_order         (order_id)
    idx_payment_transaction   (transaction_id)
    (payment_type, is_successful) [name: "idx_payment_stats"]
    (transaction_id) [unique, distinct]
  }

  checks {
    `amount > 0` [name: "ck_positive_payment"]
  }
}

Table Invoice extends Core.IdentifiableUuid
  [note: "Счет-фактура"] {

  order_id        uuid [not_null, unique, ref: > Order.Order.id, on_delete: restrict, note: "Счет для заказа"]
  invoice_number  varchar(50) [unique, not_null]
  issued_date     date [not_null, default: `current_date`]
  due_date        date [not_null]
  total_amount    decimal(12, 2) [not_null]

  indexes {
    idx_invoice_order   (order_id)
    idx_invoice_number  (invoice_number)
    (due_date, issued_date) [name: "idx_invoice_dates"]
  }

  checks {
    `due_date >= issued_date` [name: "ck_valid_due_date"]
  }
}

// ----- Логическая группировка для документации -----
Group Finance
  [note: "Финансовые операции и платежи", color: #e8f5e8] {

  Finance.PaymentType
  Finance.Payment
  Finance.Invoice
}

Диаграмма модели данных

Вот так эта модель данных будет выглядеть на диаграмме.

Знакомство с ArchDB: от формальной основы до промышленной модели

При разработке ArchDB я взял за основу DBML — лаконичный язык описания схем, знакомый многим по сервису dbdiagram.io. Его синтаксис прост и интуитивен, и я сохранил этот «дух простоты» везде, где это возможно. Но для решения задач, с которыми сталкиваются крупные проекты, DBML оказалось недостаточно. Поэтому, отталкиваясь от прототипа, я сделал несколько ключевых шагов вперёд. Причём нулевой шаг — фундаментальный — был сделан ещё до написания первой строки кода.

Шаг нулевой: строгая основа

Разработка ArchDB началась не с реализации парсера, а с создания формального описания грамматики в нотации BNF. Этого DBML полностью лишён, и именно это закладывает основу для всего остального. Благодаря строгой грамматике мы получаем:

  • однозначный парсинг моделей;

  • возможность автоматической валидации;

  • фундамент для инструментов: генераторов DDL, визуализаторов, плагинов для IDE;

  • чёткую семантику каждой конструкции, исключающую разночтения.

Формальная спецификация — это своего рода «конституция» языка, которая гарантирует, что все будущие инструменты будут говорить на одном языке.

Шаг первый: от процедурности к декларативности

В DBML вы часто описываете, как должна выглядеть таблица, но не можете чётко отделить намерение от реализации. ArchDB предлагает pure-декларативный подход: вы фиксируете желаемое состояние модели (то самое «замороженное» состояние, о котором мы говорили во введении), а всё остальное — генерация DDL, миграций, документации — ложится на инструменты. Это не просто удобно — это основа парадигмы Data as Code.

Шаг второй: от монолита к модульности

DBML поощряет хранение всей модели в одном файле. Пока таблиц двадцать — это терпимо. Когда их становится двести, а над разными частями модели работают разные команды, монолит превращается в тормоз. ArchDB вводит полноценную модульную систему: вы можете декомпозировать модель на логические схемы (Schema), выносить общие части в библиотеки, импортировать их с контролем версий и даже публиковать как внутренние пакеты. Фактически вы получаете возможность управлять схемой БД теми же инструментами, что и обычным кодом.

Шаг третий: от копипасты к повторному использованию

Это, пожалуй, самое важное нововведение. В DBML есть конструкция TablePartial, но она крайне ограничена и допускает лишь один уровень включения. В ArchDB я ввел полноценные шаблоны (Template) — абстрактные сущности, которые могут содержать колонки, индексы, проверки и наследовать от других шаблонов (и даже от таблиц). Причём поддерживается множественное наследование: таблица или шаблон могут расширять сразу несколько предков, комбинируя их функциональность.

Взгляните на разницу:

TablePartial в DBML (ограничен)

TablePartial audit_fields {
  created_at datetime
  updated_at datetime
}

Table users {
  id integer
  ...
  // Как подключить audit_fields? Через include? Это неочевидно.
}

Template в ArchDB (полноценное наследование)

Template Auditable {
  created_at timestamp [not_null, default: `now()`]
  updated_at timestamp [default: `now()`]
}

Template Trackable {
  updated_by integer [ref: > User.id]
}

// Множественное наследование
Table User extends Auditable, Trackable {
  id integer [pk, increment]
  email varchar [unique]
}

В результате таблица User автоматически получает все поля из обоих шаблонов. А если потребуется добавить во все аудируемые таблицы ещё одно поле — достаточно изменить один шаблон, и изменения применятся везде. Именно так лучшие практики перестают быть просто строчками в документе «Стандарты разработки» и становятся исполняемым кодом, который автоматически распространяется на все команды.

Шаг четвёртый: от жёстких конструкций к гибким свойствам В DBML атрибуты (например, primary keyunique) жёстко привязаны к колонкам. ArchDB делает следующий шаг: свойства (properties) могут быть добавлены к любому элементу языка — таблице, колонке, связи, индексу, перечислению. Свойства бывают двух видов: флаги (pkuniquenot_null) и пары «ключ‑значение» (default: 0note: "комментарий"color: #ff0000).

Что это даёт?

  • Расширяемость: вы можете вводить собственные свойства, которые будут нести семантику для ваших генераторов кода или инструментов визуализации.

  • Документирование: свойство note позволяет оставлять комментарии прямо в модели, делая её самодокументируемой.

  • Управление генерацией: через свойства можно управлять поведением генераторов (например, name: "order_id" задаёт физическое имя колонки в БД).

  • Вложенность: свойства могут быть вложенными, что позволяет создавать сложные конфигурации (например, java: [type: "Long", annotation: "@Id"] привязывает колонку модели ORM в java-коде).

Пример:

Table Order [note: "Основная таблица заказов", color: #e3f2fd] {
  id integer [pk, increment, name: "order_id", java: [type: "Long", annotation: "@Id"]]
  status varchar [default: 'new', note: "Статус заказа"]
}

Подведём итог. ArchDB — это не просто очередной DSL. Это язык, построенный на формальном фундаменте, предлагающий декларативность, модульность, мощное повторное использование через множественное наследование и гибкую систему свойств. Всё это делает его пригодным для проектов индустриального масштаба, где важны стандартизация, контролируемость и возможность автоматизации.

Кстати, о миграции. Если у вас уже есть модели в DBML, переход на ArchDB не потребует переписывания всего с нуля. Синтаксис описания таблиц, колонок и связей (Ref) максимально близок. Вы сможете конвертировать существующие .dbml-файлы с минимальными правками и сразу получить доступ ко всем описанным возможностям. Позже я покажу это на конкретном примере.

А теперь — к практике. Следующий раздел займёт у вас не больше минуты.

Быстрый старт: минимальная схема за 60 секунд

Теория — это хорошо, но ArchDB создан для действия. Давайте за 60 секунд напишем работающую модель интернет-магазина. Никакой магии — только код.

Минимальная схема: покупатели и заказы

Создайте файл shop.adb и поместите в него следующее:

shop.adb — первая модель на ArchDB

// Покупатель
Table Customer {
  id        integer [pk, increment]
  email     varchar(255) [unique, not_null]
  name      varchar(100)
}

// Заказ
Table Order {
  id          integer [pk, increment]
  customer_id integer
  order_date  timestamp [not_null, default: `now()`]
  total       decimal(10, 2)
  discount    decimal(10, 2) [default: 0, check: `discount >= 0 AND discount <= total`] (1)
}

// Внешний ключ
Ref: Order.customer_id -> Customer.id [on_delete: restrict]
  1. Нестандартное требование: скидка не может быть отрицательной и не может превышать итоговую сумму заказа. ArchDB позволяет выразить это прямо в модели.

Что получилось:

  • Две таблицы с первичными ключами и автоинкрементом.

  • Уникальный и обязательный email покупателя.

  • Дата заказа по умолчанию — текущий момент.

  • Поле discount со сложной проверкой (скидка не больше суммы заказа).

  • Связь «один-ко-многим» с правилом ON DELETE RESTRICT (не даст удалить покупателя, если у него есть заказы).

Уже на этом примере видно, как ArchDB сочетает простоту базового синтаксиса с возможностью выразить бизнес-правила (проверка check) прямо в схеме.

Ключевые концепции за 5 минут

Разберём элементы, из которых строится любая модель.

Колонка (Column)

Колонка — это атомарная единица данных. Её определение в ArchDB строится по простому шаблону, который имеет строгую формальную основу:

column_def ::= column_name type_name properties?

То есть: имя_колонки тип_данных необязательные_свойства.

На практике это выглядит так:

title        varchar(255) [not_null, default: '']      (1)
created_at   timestamp    [default: `now()`]           (2)
is_active    boolean      [default: false]             (3)
price        decimal(10,2)[check: `price >= 0`, note: "Цена должна быть неотрицательной"] (4)
order_id     integer      [not_null, ref: -> Order.id, on_delete: cascade] (5)
  1. Строка с ограничением длины, обязательная, значение по умолчанию — пустая строка.

  2. Дата создания, по умолчанию — текущее время.

  3. Логический флаг, по умолчанию false.

  4. Числовое поле с проверкой и комментарием (свойство note).

  5. Колонка-ссылка: свойство ref задаёт внешний ключ прямо в определении колонки. Это краткая форма, эквивалентная отдельной конструкции Ref.

Обратите внимание: default: `now()` — это выражение SQL, поэтому оно заключено в обратные кавычки. ArchDB отличает строки-литералы от выражений.

Свойство ref позволяет описать связь максимально компактно, но если вам нужно задать имя внешнеключевому ограничению или добавить несколько свойств, удобнее использовать отдельную конструкцию Ref, о которой мы поговорим чуть ниже.

Таблица (Table) и шаблон (Template)

Таблица — контейнер для колонок. Шаблон — многократно используемый фрагмент таблицы. Шаблоны поддерживают множественное наследование.

template-table.adb

// Шаблон для аудита
Template Auditable {
  created_at timestamp [default: `now()`]
  updated_at timestamp [default: `now()`]
  created_by integer
}

// Шаблон для мягкого удаления
Template SoftDeletable {
  is_deleted boolean [default: false]
  deleted_at timestamp
}

// Таблица использует оба шаблона
Table User extends Auditable, SoftDeletable {
  id       integer [pk, increment]
  username varchar [unique, not_null]
}

Таблица User автоматически получит все поля из обоих шаблонов: created_atupdated_atcreated_byis_deleteddeleted_at, плюс свои id и username. Именно так лучшие практики (аудит, мягкое удаление) превращаются в код и перестают быть просто пунктами в документе «Стандарты разработки».

Связи (Ref и Relation)

Для связи таблиц используются две конструкции: Ref (простой внешний ключ) и Relation (именованное отношение, в том числе «многие-ко-многим»).

// Простой внешний ключ (отдельная конструкция)
Ref: Order.customer_id -> Customer.id [on_delete: cascade]

// Альтернативный способ — свойство ref у колонки (показан выше)
// OrderItem.product_id integer [ref: -> Product.id]

// Отношение "многие-ко-многим"
Relation UserRoles {
  User.id <-> Role.id
}

Ref обычно достаточно для большинства связей. Relation удобен, когда нужно подчеркнуть бизнес-смысл (например, Enrollment между студентом и курсом) или когда связь реализуется через отдельную таблицу.

Какой способ выбрать? Если связь простая и не требует дополнительных свойств (имени, своего блока документации), можно использовать ref прямо у колонки. Если же связь сложная или вы хотите сделать её более заметной в модели, лучше вынести в отдельный Ref или даже в Relation.

Собираем всё вместе: модель интернет-магазина

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

shop2.adb

// Перечисление для статуса заказа
Enum OrderStatus {
  pending    [note: "Ожидает оплаты"]
  paid       [note: "Оплачен"]
  shipped    [note: "Отправлен"]
  delivered  [note: "Доставлен"]
  cancelled  [note: "Отменён"]
}

// Шаблон для аудита
Template Auditable {
  created_at timestamp [default: `now()`]
  updated_at timestamp [default: `now()`]
}

// Пользователи
Table User extends Auditable {
  id        integer [pk, increment]
  email     varchar(255) [unique, not_null]
  full_name varchar
}

// Товары
Table Product extends Auditable {
  id     integer [pk, increment]
  sku    varchar(50) [unique, not_null]
  name   varchar(255) [not_null]
  price  decimal(10, 2) [check: `price >= 0`]
  stock  integer [default: 0]
}

// Заказы
Table Order {
  id          integer [pk, increment]
  user_id     integer [not_null]
  status      OrderStatus [not_null, default: 'pending']
  total       decimal(12, 2) [check: `total >= 0`]
  order_date  timestamp [default: `now()`]
}

// Позиции заказа
Table OrderItem {
  id         integer [pk, increment]
  order_id   integer [not_null]
  product_id integer [not_null]
  quantity   integer [not_null, check: `quantity > 0`]
  price      decimal(10, 2) [not_null] // цена на момент заказа
}

// Связи
Ref: Order.user_id -> User.id [on_delete: restrict]
Ref: OrderItem.order_id -> Order.id [on_delete: cascade]
Ref: OrderItem.product_id -> Product.id [on_delete: restrict]

Этот пример уже использует:

  • перечисление (Enum) для типизированного статуса;

  • шаблон Auditable для полей аудита;

  • проверки (check) для бизнес-правил;

  • внешние ключи с явными действиями при удалении.

И всё это — в одном файле, готовом к версионированию, ревью и дальнейшей генерации DDL или диаграмм.

Что дальше? Вы только что увидели базовый синтаксис ArchDB. Но настоящая сила языка раскрывается, когда модель начинает расти. В следующем разделе мы сравним два подхода к организации кода: монолит, который быстро становится неуправляемым, и модульную структуру, которая позволяет работать над разными частями системы независимо. Но сначала…​

Как-то это всё ванильно, или как «зачитить» и «пропатчить»

Пример с интернет-магазином хорош, но в реальной жизни базы данных редко выглядят так же чисто, как на картинке. Чаще всего перед нами — результат многолетней эволюции, десятков разработчиков, срочных патчей и «временных» решений, которые стали постоянными. В такой базе поля аудита могут называться как угодно, а их набор отличается от таблицы к таблице. Одни команды любили created_at, другие — create_dt, третьи вообще обходились без updated_by, потому что «нам не надо». Давайте посмотрим, как ArchDB помогает навести порядок в этом хаосе.

Разнобой в аудиторских полях

Вот типичный фрагмент такой «органической» базы (назовём её legacy):

// Таблица заказов — писала команда А
Table orders {
  id          integer [pk]
  order_date  date
  created_ts  timestamp   // создано
  modified_ts timestamp   // обновлено
  created_by  integer
}

// Таблица клиентов — команда Б
Table customers {
  id          integer [pk]
  name        varchar
  create_date timestamp   // создано
  update_date timestamp   // обновлено
  updated_by  integer
}

// Таблица товаров — команда В (спешили, поэтому только created)
Table products {
  id       integer [pk]
  sku      varchar
  created  timestamp
  updated  timestamp
  // updated_by нет — «и так сойдёт»
}

// Таблица счетов — команда Г, любители длинных имён
Table invoices {
  id        integer [pk]
  inv_num   varchar
  ins_time  timestamp   // inserted
  upd_time  timestamp   // updated
  ins_user  integer
  upd_user  integer
}

Знакомая картина? Если нужно добавить во все таблицы поле deleted_at для мягкого удаления, придётся лезть в каждую таблицу и вручную дописывать колонку. А если ещё и имена разные — начинается настоящий ад.

ArchDB позволяет привести этот хаос к единому стандарту, не ломая существующую базу. Секрет в гибких шаблонах и возможности переопределять имена колонок через свойство name.

Сначала определим эталонные шаблоны, которые отражают наши лучшие практики:

// Базовый шаблон для идентификатора
Template Identifiable {
  id integer [pk, increment]
}

// Шаблон для времени создания
Template Created {
  created_at timestamp [default: `now()`]
}

// Шаблон для времени обновления
Template Updated {
  updated_at timestamp [default: `now()`]
}

// Шаблон для информации о создателе
Template Creator {
  created_by integer
}

// Шаблон для информации о редакторе
Template Updater {
  updated_by integer
}

Теперь «подключаем» существующие таблицы к этим шаблонам, используя наследование и свойство name, чтобы сопоставить локальные имена колонок с эталонными:

// Таблица orders приводится к стандарту
Table orders extends Identifiable, Created, Updated, Creator {
  // Локальное имя created_ts отображаем на created_at
  created_at timestamp [name: "created_ts"]
  updated_at timestamp [name: "modified_ts"]
  created_by integer   [name: "created_by"] // имя совпадает - можно не писать, но можно и вписать
  // остальные поля как есть
  order_date date
}

// Таблица customers
Table customers extends Identifiable, Created, Updated, Updater {
  created_at timestamp [name: "create_date"]
  updated_at timestamp [name: "update_date"]
  updated_by integer   [name: "updated_by"]
  name varchar
}

// Таблица products — здесь нет updated_by, поэтому наследуем только нужное
Table products extends Identifiable, Created, Updated {
  created_at timestamp  [name: "created"]
  updated_at timestamp  [name: "updated"]
  sku varchar
}

// Таблица invoices
Table invoices extends Identifiable, Created, Updated, Creator, Updater {
  created_at timestamp  [name: "ins_time"]
  updated_at timestamp  [name: "upd_time"]
  created_by integer    [name: "ins_user"]
  updated_by integer    [name: "upd_user"]
  inv_num varchar
}

Что мы получили?

  • Все таблицы теперь «знают» о едином стандарте. Генераторы DDL, документации или кода могут обращаться к полям created_atupdated_at и т.д., даже если в реальной базе они называются иначе.

  • Если потребуется добавить новое общее поле (например, deleted_at), достаточно добавить его в соответствующий шаблон — и оно автоматически появится во всех таблицах, которые этот шаблон наследуют. При этом физическое имя можно будет переопределить через name, если в какой-то таблице оно уже занято.

  • Мы не потеряли старые данные и не сломали существующие запросы — физическая схема осталась прежней.

  • Мы получили единообразную логическую модель, ее легко читать и понимать.

Мягкое удаление: флаги и даты

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

Пусть у нас есть:

  • orders — поле deleted_flag (1/0)

  • customers — поле is_deleted (boolean)

  • products — поле deleted_date (timestamp, если не NULL — удалено)

  • invoices — вообще без мягкого удаления

Мы хотим ввести единый интерфейс: все таблицы должны иметь возможность проверять, удалена ли запись, и при необходимости получать дату удаления. Но менять физическую схему нельзя (пока?..).

Решение — создать два шаблона: SoftDeleteFlag и SoftDeleteDate, и подключать их выборочно, переопределяя имена.

Template SoftDeleteFlag {
  is_deleted boolean [default: false]
}

Template SoftDeleteDate {
  deleted_at timestamp
}

// В orders есть флаг, но нет даты
Table orders extends SoftDeleteFlag {
  is_deleted boolean [name: "deleted_flag"]
}

// В customers есть флаг
Table customers extends SoftDeleteFlag {
  is_deleted boolean [name: "is_deleted"] // имя совпадает
}

// В products есть дата удаления
Table products extends SoftDeleteDate {
  deleted_at timestamp [name: "deleted_date"]
}

// В invoices мягкого удаления нет — шаблоны не подключаем
Table invoices {
  id integer [pk]
  // ...
}

Теперь в коде модели можно единообразно работать с is_deleted и deleted_at, а генераторы будут создавать правильные DDL с учётом физических имён.

Композитные ключи с разным порядком и именами

Составные первичные ключи — ещё одна зона хаоса. В одной таблице ключ может быть (order_id, line_number), в другой — (product_id, attribute_id), и ограничения названы как попало.

ArchDB позволяет явно описать ключ через индекс со свойством pk и задать ему имя.

Table order_items {
  order_id    integer
  line_number integer
  quantity    integer
  indexes {
    (order_id, line_number) [pk, name: "pk_order_items"]
  }
}

Table product_attributes {
  product_id   integer
  attribute_id integer
  value        text
  indexes {
    (product_id, attribute_id) [pk, name: "pk_product_attrs"]
  }
}

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

Вычисляемые поля с разной логикой

Современные СУБД поддерживают виртуальные/сохранённые вычисляемые колонки. В легаси такое могли реализовать через триггеры или на стороне приложения. ArchDB позволяет декларировать вычисляемые поля, а при генерации DDL они превратятся в соответствующие конструкции.

Предположим, в таблице invoices раньше tax_amount вычислялось в коде, а мы хотим перенести логику в БД:

Table invoices {
  id          integer [pk]
  subtotal    decimal
  tax_rate    decimal
  tax_amount  decimal [generated: `subtotal * tax_rate`]
  total       decimal [generated: `subtotal + tax_amount`]
}

А в таблице orders скидка рассчитывается иначе:

Table orders {
  id                integer [pk]
  total_before      decimal
  discount_percent  decimal
  discount_amount   decimal [generated: `total_before * discount_percent / 100`]
  total             decimal [generated: `total_before - discount_amount`]
}

Если в дальнейшем мы решим привести названия к единому виду, опять поможет name.

Индексы с условиями для разных бизнес-правил

Часто индексы создают под конкретные запросы, и условия могут быть самыми разными. ArchDB поддерживает свойство where для индексов.

Table orders {
  id          integer [pk]
  status      varchar
  created_at  timestamp
  indexes {
    (created_at) [where: `status = 'pending'`, name: "idx_pending_orders"]
    (created_at) [where: `status = 'shipped'`, name: "idx_shipped_orders"]
  }
}

А если в разных таблицах похожие индексы называются по-разному, можно через name привести к единому стилю.

Вложенные свойства для мета-информации

ArchDB допускает вложенные свойства, что можно использовать для передачи дополнительных указаний генераторам. Например, пометить поля, которые должны маппиться на определённые типы в Java.

Table user {
  id     integer [pk, java: [ type: "Long",   annotation: "@Id"    ]]
  email  varchar [    java: [ type: "String", annotation: "@Email" ]]
}

Это уже экзотика, но пример показывает, что язык можно расширять под свои нужды.

Небольшой итог

ArchDB даёт возможность навести порядок в базе, не устраивая «большой взрыв» и не переписывая всё с нуля. Постепенно, шаг за шагом, можно привести любую, самую запущенную схему к единообразию. Единообразию на уровне логического описания модели в формате ArchDB, всё богатство и разнообразие физических БД проекта останется нетронутым. Нетронутым пока? Вам начинают приходить в голову мысли о приведении в порядок и физических БД? Это всё из-за ArchDB, есть у него такое свойство — наталкивать на мысли.

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

От простого к сложному: монолит или модули?

Вы только что написали свою первую модель на ArchDB. В маленьком проекте можно хранить всё в одном файле — это удобно и просто. Но что произойдёт, когда таблиц станет пятьдесят, а над разными частями системы (каталог, заказы, финансы) будут работать разные команды? Монолитная модель быстро превращается в проблему.

Давайте посмотрим на примере того же интернет-магазина.

Монолит: всё в одной куче

Представьте, что все таблицы — UserProductOrderPaymentInvoice — описаны в одном огромном файле ecommerce.adb. Связи между ними переплетены, и чтобы понять, как устроен, скажем, модуль финансов, приходится продираться через десятки посторонних таблиц.

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

Преимущества монолита

Недостатки

Простота на старте

Сложность навигации и восприятия при росте

Все связи «на виду»

Высокая связанность, сложно выделить поддомен

Затруднено параллельное развитие разными командами

Модули: разбиваем на части

ArchDB предлагает другой путь — модульную организацию. Модель разбивается на логические схемы (Schema), каждая из которых соответствует бизнес-поддомену: CoreProductOrderFinance. Общие шаблоны (например, Auditable) выносятся в ядро и импортируются в другие модули.

Вот как может выглядеть файл модуля заказов (Order.adb):

Package com.mycompany.ecommerce.Order

import "com.example.ecommerce.Core.adb"       (1)
import "com.example.ecommerce.Product.adb"    (1)

Table Order extends Core.IdentifiableUuid, Core.Auditable { (2)
  user_id uuid [ref: > Core.User.id]
  total decimal
}

Table OrderItem {
  order_id uuid [ref: > Order.id]
  product_id uuid [ref: > Catalog.Product.id] (3)
  quantity integer
}
  1. Импорт общей библиотеки (ядро) и модуля продуктов, соответственно из файлов Core.adb и Product.adb.

  2. Наследование от шаблонов IdentifiableUuid и Auditable, определённых в Core.adb.

  3. Ссылка на сущность из импортированного модуля.

Что мы получили:

  • Независимость команд. Команда «Заказы» может менять свой модуль, не затрагивая «Каталог» или «Финансы», пока не меняет публичный интерфейс.

  • Чёткие границы. Каждый модуль явно импортирует только то, что ему нужно.

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

  • Упрощённое понимание. Заглянув в папку order, вы сразу видите все сущности, относящиеся к заказам, и их зависимости.

Этот подход масштабируется на проекты любого размера. В спецификации ArchDB вы найдёте более полный пример с разделением на четыре модуля и экспортом общих шаблонов.

Вывод: модульность — не просто «красивая опция», а необходимость для промышленной разработки. ArchDB даёт для этого все инструменты: схемы, импорт/экспорт, пакеты и контроль версий.

Организация кода: как управлять моделью, когда она перестаёт помещаться в один файл

Модульность — это не только про разбиение на файлы, но и про правила, по которым эти файлы общаются друг с другом. ArchDB даёт целый арсенал средств, чтобы большая модель оставалась прозрачной, управляемой и удобной для командной работы.

Пространства имён: Package

Когда вы начинаете делить модель на модули, неизбежно возникает риск конфликта имён. Что, если в модуле Finance и в модуле Product есть таблица Transaction? Чтобы избежать путаницы, ArchDB предлагает механизм пакетов (Package).

Объявление пакета помещается в начале файла и задаёт пространство имён для всех определений в этом файле:

Package com.mycompany.ecommerce.Order

// Теперь полное имя таблицы Order — com.mycompany.ecommerce.Order.Order
Table Order { ... }

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

Проект: глобальные настройки

Конструкция Project задаёт параметры, общие для всей модели: целевую СУБД, версию, автора, описание. Обычно Project помещают в главный файл проекта (например, ecommerce.adb).

Project ECommercePlatform [
  database: "postgresql",
  version: 2.5.0,
  author: "Platform Team",
  note: "Модель данных интернет-магазина"
]

Эти настройки могут использоваться генераторами DDL и другими инструментами.

Схемы: логические модули

Схема (Schema) — это основной контейнер для группировки сущностей по смыслу. В отличие от просто разнесения по файлам, схема создаёт чёткие границы: всё, что объявлено внутри Schema, логически принадлежит одному модулю. Схемы могут соответствовать схемам в базе данных (например, finance в PostgreSQL), а могут быть чисто организационными.

Схема создает вложенное в пакет пространство имен.

Пример схемы Core:

Schema Core [note: "Общие шаблоны и базовые сущности"] {
  Template Auditable { ... }
  Table User { ... }
}

Импорт и экспорт: повторное использование как в коде

Самый мощный механизм модульности — директивы import и export. Они позволяют создавать настоящие библиотеки многократно используемых компонентов.

Допустим, у вас есть централизованный репозиторий «Стандарты компании» с шаблонами AuditableSoftDeleteIdentifiableUuid. Вы оформляете его как экспортируемый модуль:

// Файл com/company/standards.adb
Package com.company.standards

export Auditable as "com.company.standards.Auditable" version 1.2.0
export SoftDelete as "com.company.standards.SoftDelete"

Template Auditable { ... }
Template SoftDelete { ... }

А в любом другом проекте просто импортируете нужную версию:

import "com.company.standards.adb" version 1.2.0

Table Order extends Auditable { ... }

Это кардинально меняет способ распространения лучших практик. Вместо многостраничных PDF-документов «Требования к проектированию» разработчики получают работающий код, который можно сразу использовать, и который гарантирует соблюдение стандартов.

Метаданные: документируем файл

Блок Metadata позволяет добавить к файлу служебную информацию: автора, дату создания, описание, лицензию. Это особенно полезно для библиотек, распространяемых между командами.

Metadata [
  author: "Иванов И.И.",
  version: 1.0.0,
  description: "Шаблоны аудита для всех проектов компании",
  license: "Internal"
]

Собираем всё вместе

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

com/example/ecommerce/
├── Core.adb      // Пакет com.example.ecommerce.Core, экспорт шаблонов
├── Product.adb   // Пакет com.example.ecommerce.Product, импорт Core и экспорт Product
├── Order.adb     // Пакет com.example.ecommerce.Order, импорт Core и Product
└── Finance.adb   // Пакет com.example.ecommerce.Finance, импорт Order и Core

Каждый модуль имеет собственное пространство имён, чёткие зависимости (через импорт), и все они собираются в единую модель без конфликтов.

Резюме

  • Package — пространство имён, предотвращает конфликты.

  • Project — глобальные настройки модели.

  • Schema — логический контейнер для сущностей одного домена, углубление пространств имен.

  • Import/Export — механизм повторного использования и версионирования.

  • Metadata — самодокументирование файлов.

Используя эти инструменты, вы сможете строить модели любого масштаба, сохраняя их понятными и поддерживаемыми — точно так же, как вы пишете обычный код.

В следующем разделе мы на практике покажем, как мигрировать существующую модель из DBML в ArchDB и сразу получить все описанные преимущества.

Миграция с DBML: переносим существующую модель без боли

У вас уже есть модель на DBML? Отлично — вы не начинаете с нуля. ArchDB спроектирован так, чтобы переход был максимально плавным. Синтаксис описания таблиц, колонок и базовых связей практически идентичен. Достаточно заменить расширение файла с .dbml на .adb и поправить пару мелочей — и вы уже в мире модульности и шаблонов.

Давайте посмотрим на конкретном примере. Возьмём типичную DBML-схему блога:

Исходный файл blog.dbml

TablePartial blogEntity {
  id integer [primary key]
  created_at timestamp
  updated_at timestamp
}

Table users {
  username varchar
  email varchar [unique]
  ~blogEntity
}

Table posts {
  user_id integer [ref: > users.id]
  title varchar
  body text
  status varchar [default: 'draft']
  ~blogEntity
}

Table tags {
  name varchar [unique]
  ~blogEntity
}

Table post_tags {
  id integer [primary key]
  post_id integer [ref: > posts.id]
  tag_id integer [ref: > tags.id]
}

Шаг 1. Механический перенос

Просто переименуйте файл в blog.adb и замените [primary key] на [pk]. ArchDB понимает тот же синтаксис для связей (Ref:), так что внешние ключи останутся рабочими. На этом этапе модель уже будет валидной.

Шаг 2. Добавляем шаблоны

Обратите внимание: поля created_at и updated_at повторяются в таблицах usersposts и tags. В DBML они описаны в TablePartial. В ArchDB мы выносим их в шаблон:

Template Identifiable {
  id integer [pk, increment]
}

Template BlogEntity extends Identifiable {
  created_at timestamp [default: `now()`]
  updated_at timestamp [default: `now()`]
}

Теперь подключаем шаблон к таблицам через наследование:

Table users extends BlogEntity {
  username varchar [not_null]
  email varchar [unique, not_null]
}

Table posts extends BlogEntity {
  user_id integer [not_null]
  title varchar(500) [not_null]
  body text
  status varchar [default: 'draft']
}

Table tags extends BlogEntity {
  name varchar [unique]
}

Сразу становится чище, и добавление новых общих полей (например, deleted_at) потребует правки только в одном месте.

Шаг 3. Типизируем статусы

ArchDB, как и DBML, предлагает перечисления:

Enum PostStatus {
  draft
  published
  archived
}

И задаём тип колонки:

Table posts extends BlogEntity {
  // ...
  status PostStatus [default: 'draft']
}

Теперь ошибиться невозможно: база данных (или генератор) гарантирует, что попадёт только одно из трёх значений.

Шаг 4. Улучшаем связи

Связь многие-ко-многим между постами и тегами в DBML выражена через явную таблицу post_tags. Это рабочий, но «механический» способ. ArchDB позволяет описать ту же связь более декларативно через Relation:

Relation PostTagging {
  posts.id <-> tags.id
}

При генерации DDL такая конструкция может превратиться в ту же таблицу PostTagging, но в модели она занимает одну строку и явно передаёт намерение «у поста может быть много тегов, у тега — много постов».

Итог: что мы получили

Сравните исходный DBML-файл и получившуюся ArchDB-модель:

  • Кода стало меньше — наследование шаблонов позволяет лаконично записывать повторяющиеся элементы.

  • Модель безопаснее — перечисления исключают «магические строки».

  • Появилась семантика — Relation явно говорит о типе связи.

  • Мы заложили фундамент для модульности — теперь можно разнести таблицы по разным схемам и подключать общие шаблоны из библиотек.

И главное — на каждом шаге модель оставалась работоспособной. Вы можете проводить миграцию постепенно: сначала просто переименовать файл, потом добавлять шаблоны, потом перечисления. ArchDB не требует «big bang»-перехода.

Резюме

Что было в DBML

Что стало в ArchDB

Выгода

[primary key]

[pk]

Простая замена

Фрагменты таблиц (TablePartial)

Шаблоны (extends)

Гибкий механизм множественного наследования

Перечисления (Enum)

Перечисления (Enum)

Типобезопасность

Таблицы-связки

Relation

Явное выражение намерения

Один файл

Модули + импорты

Масштабирование

Теперь, когда модель готова, можно подключать инструменты: генераторы DDL, визуализаторы, плагины для IDE. Но это уже тема следующей статьи.

Что дальше? Подводим итоги и заглядываем в будущее

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

  • Декларативность и Data as Code — вы фиксируете желаемое состояние модели, а генераторы делают всю черновую работу.

  • Модульность — даже огромную схему можно разбить на независимые модули (схемы), которые развиваются разными командами без конфликтов.

  • Шаблоны и множественное наследование — лучшие практики (аудит, мягкое удаление) перестают быть словами в PDF и становятся кодом, который автоматически распространяется на все таблицы.

  • Гибкие свойства — notecolorname и даже пользовательские атрибуты позволяют добавить семантику любому элементу модели.

  • Плавная миграция с DBML — вы можете начать пользоваться ArchDB уже сегодня, просто переименовав файлы, и постепенно внедрять более мощные возможности.

Если вы ещё не пробовали ArchDB в деле — самое время открыть редактор, создать файл .adb и описать пару сущностей вашего текущего проекта. Полная спецификация языка поможет не заблудиться в деталях.

Что в следующей серии? В этой статье мы сознательно обошли стороной инструментальную экосистему. В следующий раз поговорим о том, как настроить IDE (IntelliJ IDEA, OpenIDE, GigaIDE) для максимальной продуктивности: подсветка синтаксиса, автодополнение, инспекции, генерация диаграмм прямо из кода и даже встраивание диаграмм в AsciiDoc-документацию через include::foo.adb[]. Это будет разговор о том, как сделать ArchDB вашим основным рабочим инструментом.

А пока — жду ваших вопросов и замечаний в комментариях. Расскажите, с какими проблемами в проектировании БД сталкиваетесь вы и какие возможности ArchDB кажутся вам наиболее полезными. А может, у вас есть предложения по улучшению языка?