Несмотря на доступность огромного выбора различных хранилищ данных NoSQL, реляционные базы данных остаются одним из самых популярных вариантов сохранения данных, несмотря на несоответствие импеданса между базами данных SQL и кодом приложения, помимо дополнительных усилий, необходимых для взаимодействия с любым тип базы данных, во многих случаях остается проблемой, и это особенно верно, когда нет необходимости во всех функциях, предоставляемых автономной СУБД, таких как серверное хранилище или многопользовательская поддержка, а нужен только способ локального сохранения данных .
В этой серии статей будет рассмотрен другой подход, в котором используется встраиваемый язык программирования Cell, имеющий встроенную поддержку реляционной модели. Вы можете написать схемы своей базы данных в Cell вместе со всей связанной логикой (как запросы, так и обновления), и на основе этого компилятор Cell сгенерирует соответствующий класс C # или Java, который вы можете включить в существующий проект. В дополнение к предоставлению всех функций, которые вы ожидаете от любой встроенной базы данных SQL, таких как постоянство, транзакции и проверки целостности данных, Cell предлагает следующие преимущества:
- Обычно вообще не требуется выполнять объектно-реляционное сопоставление. Компилятор позаботится обо всех необходимых преобразованиях данных. При небольшом тщательном проектировании классы, сгенерированные компилятором Cell, могут быть почти так же просты в использовании, как и рукописные.
- Когда вы реализуете логику своей базы данных, вы не ограничены в своих возможностях из-за отсутствия выразительности странного подъязыка данных, такого как SQL, но в вашем распоряжении есть язык программирования общего назначения, который позволяет вам реализовать любое поведение.
- Сгенерированный код выполняется очень быстро, предлагая производительность, схожую с производительностью эквивалентного рукописного объектно-ориентированного кода C # или Java.
- В вашем распоряжении гораздо лучшая модель данных, чем модель, предоставляемая SQL: среди прочего, столбцы ваших отношений / таблиц могут иметь любой определяемый пользователем тип, иерархии наследования могут быть смоделированы напрямую, а схемы могут быть определены по модульному принципу.
- Сочетание универсальных возможностей программирования, скорости и очень гибкой системы типов позволяет Cell естественным образом моделировать наборы данных, которые вы обычно не храните в реляционной базе данных. Он также может быть альтернативой, например, базе данных графов, такой как Neo4j, или хранилищам документов, или просто записи и чтению непосредственно из / в файл JSON.
Более того, Cell - это не просто инструментарий для создания встроенных баз данных, а полнофункциональный встраиваемый язык программирования, который можно использовать для моделирования и реализации даже тех частей приложения, которые не нужно сохранять, но которые могут принести пользу всем. функции Cell, недоступные в традиционных языках, такие как модель данных очень высокого уровня, транзакции и возможность воспроизводить выполнение программы и легко проверять ее состояние. Части приложения, которые сами по себе не являются постоянными, но которые тесно связаны с постоянными, особенно вероятно выиграют от реализации в Cell, поскольку это означает, что вы можете использовать общий язык и обозначения для всех из них.
Однако одним из внутренних ограничений Cell является то, что его можно использовать только для создания баз данных в памяти: весь набор данных загружается в ОЗУ при запуске, и с этого момента к диску касаются только для сохранения изменений в данных. Для наборов данных, которые слишком велики для размещения в памяти, Cell в настоящее время не подходит.
База данных Northwind в Cell
В этой серии статей мы будем использовать базу данных Northwind, образец базы данных, используемый Microsoft для демонстрации возможностей SQL Server и Microsoft Access. Он содержит данные о продажах Northwind Traders, фиктивной компании по импорту / экспорту специализированных продуктов питания. Вот структура базы данных:
Вот как выглядит определение схемы базы данных в Cell:
Мы не будем здесь подробно описывать синтаксис (это тема для будущих публикаций этой серии), но мы выделим наиболее важные отличия от SQL. В качестве примера мы воспользуемся следующим подмножеством таблицы Товары:
Первое существенное отличие состоит в том, что, хотя это не сразу ясно из синтаксиса, данные, которые в SQL хранятся в одной таблице, разделены на несколько различных отношений в Cell. Первое - это унарное отношение (то есть таблица с одним столбцом, которая представляет собой просто набор) product, единственная цель которого - хранить идентификаторы всех товары в базе:
Как видите, каждый продукт в базе данных идентифицируется не целым числом или строкой, как в SQL, а значением определенного пользователем типа ProductId в этот конкретный случай:
Значения типа ProductId - это просто целые числа с тегами. Если вы не знакомы с языками функционального программирования, такими как Haskell, F # или Elm, подумайте об этом как о более или менее эквиваленте структуры / класса с одним анонимным полем типа long в C # или Java:
Существует множество типов, подобных этому, в верхней части приведенного выше определения схемы (строки 1–9). Использование определяемых пользователем типов для идентификации сущностей в вашем домене - одна из вещей, которые позволяют моделировать иерархии наследования. Эти типы не несут никакой информации о соответствующих объектах, атрибуты которых хранятся в отдельных отношениях:
Каковы преимущества использования отношений атрибутов? Во-первых, «атомарными» отношениями гораздо легче манипулировать с помощью языка общего назначения. Мы скоро увидим, как это работает. С другой стороны, «широкие» таблицы в стиле SQL могут использоваться только для хранения однозначных атрибутов: многозначные (представьте, например, что у клиента есть несколько телефонных номеров или адресов) в любом случае должны храниться в отдельной таблице. Вам также понадобится проблемная функция, например NULL для необязательных атрибутов. С другой стороны, атомарные отношения могут единообразно кодировать любую комбинацию необязательных или обязательных, одно- или многозначных атрибутов, просто определяя соответствующие ключи и внешние ключи.
Когда вы пишете схему, подобную приведенной выше, вы определяете то, что на жаргоне Cell называется реляционным автоматом. А пока подумайте о реляционном автомате как о некоем большом классе, который содержит внутри целую реляционную базу данных.
Составление базы данных
Теперь, когда мы определили схему базы данных, мы можем попробовать ее скомпилировать. Это сгенерирует следующий класс C #:
На данный момент существует всего несколько методов, которые сохраняют или загружают состояние базы данных в объект типа System.IO.Stream или из него. Cell сохраняет данные в настраиваемом, удобочитаемом текстовом формате. Думайте об этом как о разновидности JSON, но с отношениями и алгебраическими типами данных. В будущей версии также будет добавлена возможность сохранять фактический JSON для лучшей совместимости и двоичную версию настраиваемого текстового формата для повышения эффективности.
Вы можете увидеть, как выглядит база данных Northwind при сохранении на диск здесь. Если вы хотите узнать больше о модели данных Cell, вы можете найти официальную документацию здесь.
Вы также можете сгенерировать класс Java, если хотите (и поддержка других целевых языков будет добавлена в будущем), но здесь мы будем придерживаться генератора кода C #, потому что на данный момент классы, которые он создает, имеют более приятный интерфейс, благодаря также ряду функций C # (например, кортежи, именованные кортежи, частичные классы и именованные и необязательные аргументы), которые не имеют эквивалента в Java.
Запрос базы данных
Пришло время написать реальный код. Начнем с этого простого SQL-запроса:
В Cell вам нужно написать метод для автомата Northwind, определенного выше:
Первая строка в теле метода (строка 4) является выражением понимания набора: он выполняет итерацию по всем идентификаторам сотрудников в отношении employee и для каждого из них создает кортеж из трех элементов, содержащий идентификатор, имя и фамилию сотрудника. Выражения first_name (e) и last_name (e), где e - идентификатор сотрудника, получить значение соответствующего атрибута. Вторая строка сортирует набор по фамилии, третье поле в кортеже.
Теперь мы можем перекомпилировать базу данных. Созданный класс C # теперь имеет новый метод SortedEmployeesNames ():
Сигнатура нового метода почти идентична сигнатуре метода Cell, от которого он происходит. Единственное отличие - это тип первого поля возвращаемых кортежей. Чтобы перемещать данные между Cell и C # частями базы кода, компилятор должен сопоставить каждый тип Cell с соответствующим типом C #. В некоторых случаях сопоставление тривиально: это то, что происходит в приведенном выше коде со строками, кортежами и массивами. Для тегированного значения, такого как EmployeeId, компилятор просто отбрасывает тег (который известен во время компиляции и, следовательно, не несет никакой информации) и возвращает значение, заключенное внутри.
Теперь мы попробуем кое-что посложнее: для каждого заказа мы хотим рассчитать общую сумму, которую нам нужно взимать. Это наш SQL:
и это версия Cell:
Версия Cell длиннее, но написана в более модульной форме. Это пригодится позже. Это соответствующие методы в сгенерированном классе C #:
Обратите внимание, что orders_totals возвращает записи вместо кортежей, которые по умолчанию сопоставляются с именованными кортежами в C #.
Следующий запрос SQL также вычисляет итоги для каждого заказа, но делает это только для заказов, которые были отгружены между двумя заданными датами, и сортирует результаты по дате отгрузки:
Это реализация Cell:
и это сигнатура соответствующего метода в сгенерированном классе C #:
Вы можете увидеть, как тип Date в ячейке был автоматически сопоставлен с System.DateTime в C #.
Следующие шаги
Это все для части 1. В части 2 мы увидим, как реализовать более сложные запросы, которые трудно выразить в SQL, но легко написать на языке общего назначения, и как мы может настраивать интерфейсы сгенерированных классов.
Весь код и примеры для частей 1 и 2 доступны на github для Windows или Linux / macOS. Вы можете запускать все запросы, показанные в этих сообщениях, а также легко реализовать свои собственные.