... с объектной ориентированностью, сериализацией, reflection, полиморфизмом, визуальным программированием, no-code, блэкджеком и шлюхами - и это на MS SQL 6.5 1995 году!
Знакомые с историей IT при слове "однозвенка" вспомнят dBase и Clipper. Однако, я расскажу об ERP однозвенке. Интерфейсная программа для этой ERP общалась с базой через несколько интерфейсных таблиц и несколько процедур. То есть фактически она является браузером, который за слой не считается. Да, это #ненормальное программирование, которое дало ряд уникальных свойств.
Иногда ведь хочется ручку настройки повернуть до максимума и довести идею до абсурда логического завершения. Как говорили родители одному из героев фильма "О чем говорят мужчины, продолжение" - "Так ты дойдешь до черте-чего!", а он думал "Так уже хочется взять и дойти до этого черте-чего!".
Система называлась Ultimа-S, она же Nexus. Судьба ее была незавидна - эта ERP делалась на продажу, а продаж не было. Что, впрочем, было естественно - мы, в том числе автор этих строк, не имели ни малейшего понятия о маркетинге и продажах. Зато я имел удовольствие развлечься за счет работодателя. Итак,
Поехали
Берем установочные скрипты и устанавливаем систему на MS SQL 2019. Правда, базу пришлось загнать в compatibility mode. Запускаем exe. Он, кстати, крошечный (мне пришлось найти ntwdblib.dll):
![](https://habrastorage.org/getpro/habr/upload_files/d27/32d/eb7/d2732deb78d3e041f9c61bb45423303d.png)
и в памяти занимает примерно столько же, сколько Notepad:
![fg fg](https://habrastorage.org/getpro/habr/upload_files/f67/bc3/f40/f67bc3f403ff42990ebb5f3a500bb460.png)
Запускаем nexus.exe, и открываем иерархию классов:
![](https://habrastorage.org/getpro/habr/upload_files/6b4/794/d0e/6b4794d0e415ad029c4f0c98f7016438.png)
При использовании возникает очень интересное чувство, которое я не испытывал со времен MS DOS. Многие операции отрабатывают мгновенно. Нет, реально мгновенно. В современных программах, даже WinForms (я уж не говорю про Web), код настолько тяжел, столько динамически отводит памяти и столько раз вызывает GC, что глаз замечает видимые задержки. Мы к ним привыкли и не замечаем. Но когда программа реально рисует окно после нажатия на клавишу за время одного фрейма экрана, то это удивляет. А когда-то, во времена MS DOS, это было нормально.
Как это работает?
Вся коммуникация с сервером происходит через несколько процедур и три (с небольшим) таблицы. Хочется раскрыть папку - вызываем процедуру, передавая id папки, а в результирующую таблицу сваливается список документов, которые там находятся. Причем клиента не интересует, как этот список получился.
Делаем right click и хотим получить список свойств?
Тоже через таблицу. Пользователь видит user friendly имена свойств, а еще у них есть системные id, среди которых - как правило 'view' (read only просмотр по умолчанию) и 'edit' (основное редактирование).
![](https://habrastorage.org/getpro/habr/upload_files/820/509/47d/82050947dd1b9fe4061d0137b9068b63.png)
Наконец, мы выбрали свойство и хотим посмотреть документ. Результат выдается через таблицу Detailed где есть id полей, значения (есть колонки для разных типов) и колонку форматирования. Это поле в первой букве содержит тип значения int, float, money, string, document (который на самом деле тоже int), а дальше название для человека и некие теги указывающие поведение, например: fширина^min=0^max=10.0
Вас конечно заинтересовало, если таблицы для коммуникации общие, то как могут одновременно работать много клиентов? Все очень просто - у всех этих таблиц есть поле spid (=@@spid). А все клиенты открывают ровно одну коннекцию и используют только ее (да, такой дизайн был нормой). Никаких connection pools. Представляете как легко сделать пессимистичные блокировки - по spid вы точно знаете что клиент не отсох и даже с какого hostnamе он работает!
Обычно клиент располагает поля сверху вниз, но для важных документов могут быть задизайнены красивые формы. И да, теперь языком дизайна был бы наверняка HTML, а языком сценариев (min^ max^ и полее сложные пересчетом totals) был бы JS.
Объектная ориентированность
Все документы в системе произведены от общего класса Doc. При создании производных классов создаются документы типа Класс, осуществляя своеобразный Reflection. Часть иерархии классов вы могли видеть на первом скриншоте. Классы могут быть и абстрактными.
При выполнении операции (раскрытия папки, получения свойств и отображения) вызываются процедуры с именами вида Class_operation_ID_, причем вначале идет волна 'pre' свойств, начиная от Doc к листьевому классу, а потом 'post', в обратном направлении, от листа к корню. Как правило, все свойства pre, а post полезен для удаления данных (удаления лучше делать обратном порядке, от detail таблиц к master).
Для pre свойство очередного класса 'видит' что натворили классы под ним, поэтому может не просто добавить поле или свойство, но и 'надругаться' над тем, что сделано до него - переименовать свойство или вообще убрать его, сделать поля read-only, дать им умолчания или вообще скрыть поля с помощью специального флага (но не удалять - иначе будут проблемы при записи)
Ниже вы видите пример процедур на SQL, к которому был прикручен самописный макроязык. Обратите внимание как производный класс изменяет friendly name свойства пользователя, а также иногда добавляет новое свойство.
![](https://habrastorage.org/getpro/habr/upload_files/76a/ca8/c4c/76aca8c4c9a8653f503572dcf34de70c.png)
Работа через Detailed давала еще одну интересную возможность: сериализация. Ведь можно просто вызывать свойство view (например), получить ответ, и запомнить его. Получалась мертвая копия, ксерокс, но выглядела она как настоящий документ - только была read-only.
Аналогично, Аудит (история изменений) работала на уровне корневого класса Doc, сравнивая Detailed до и после редактирования. Все производные классы были освобождены от необходимости следить за аудитом редактирования!
No-code
Система позволяла мастерить новые документы прямо через интерфейс. Сейчас покажу пример. Создадим класс, производный от пользователя:
![](https://habrastorage.org/getpro/habr/upload_files/cb1/c4b/808/cb1c4b808a608fee1cc9c97d5472a1d5.png)
В диалоге создания класса заполним необходимые поля. Как видите, я лишь прохожусь по самым верхам - в системе много что было еще.
![](https://habrastorage.org/getpro/habr/upload_files/cc0/fb0/d2c/cc0fb0d2c1dc96daaf973c2664168989.png)
Теперь через right click на образовавшемся классе создадим расширение, то есть новое поле - число пальцев от 5 до 10.
![](https://habrastorage.org/getpro/habr/upload_files/ed1/be2/d2f/ed1be2d2f039fc458a854db0460927a1.png)
Теперь мы создаем сотрудника, указав число пальцев 5, и записываем его.
![](https://habrastorage.org/getpro/habr/upload_files/29c/98b/3c5/29c98b3c56415c3899000daacf33534d.png)
Посмотрим на результат. Новое поле добавилось внизу:
![](https://habrastorage.org/getpro/habr/upload_files/896/a5b/e62/896a5be62b09f077b2d73664e71c40ca.png)
Сравним обычного пользователя и VIP пользователя. Благодаря полиморфизму (в одной папке могут быть документы любого типа) мы можем положить в папку и обычного пользователя и VIP:
![](https://habrastorage.org/getpro/habr/upload_files/896/ed3/ff4/896ed3ff443a8c8a1af98596b78f5a0d.png)
С помощью No-code можно было создать сложные документы, с таблицами в духе товар-количество-цена и полем общая сумма, которая пересчитывалась автоматически - и это все без единой строчки кода. Данные ('расширения'), правда, хранились в универсальной таблице, что плохо с точки зрения производительности. Поэтому такие вещи предназначались для маленьких дополнений, а основной код все-таки писался на TSQL
Вершиной no-code был user assistant - метод переопределения поведения любых полей.
![](https://habrastorage.org/getpro/habr/upload_files/3cc/fd0/799/3ccfd0799b8646413b126cf33097cc73.png)
Администратор мог для любого документа, для любого поля, для определенной группы пользователей добавить умолчание, сделать поле read-only (что вместе с умолчанием заставляет использовать только определенное значение), или спрятать поле вовсе. Для полей выбора документов можно было изменить Root (папку начиная с которой выбираются документы для ввода) - например, пусть Вася Пупкин при создании платежки сможет указывать только этих трех клиентов (накидаем ссылки на этих клиентов в отдельную папочку)
Фильтрация
В системе были как обычный папки, похожие на папки для файлов, куда можно было перетаскивать документы мышкой, так и пользовательские фильтры:
![](https://habrastorage.org/getpro/habr/upload_files/001/31f/08d/00131f08d83eec760a9992f1b5c462e2.png)
Через систему событий опрашивались все классы - а по каким полям ты умеешь фильтроваться?
![](https://habrastorage.org/getpro/habr/upload_files/bd0/3ee/4e6/bd03ee4e6f5e771aee86aa4681acd014.png)
Далее из этих кирпичиков пользователь мог собрать любой фильтр. Конечно, в такую папку документы перетаскивать было запрещено.
![](https://habrastorage.org/getpro/habr/upload_files/3cc/fd0/799/3ccfd0799b8646413b126cf33097cc73.png)
Заключение
С тех пор я не видел потенциально более гибких систем. А монстры типа SAP R/3 живут и процветают...
Nehc
Вот так смотришь на это все…
И понимаешь, что все-таки 1С свернула не туда!
ipanshin
Тонкий клиент 1С целиком и полностью копирует основную идею ultima or nexus резалтсет запроса хранить на сервере. Правда при этом программирование усложняется на порядок так как необходимо обеспечивать разделение программного текста на серверный и клиентский. Кстати через nexus можно совершенно спокойно рассматривать метаданные 1с или любых других mssql систем. У меня был опыт сопровождения биллинговой системы BillOnLine, в которую я внедрил nexus скрипт и тем самым обеспечил альтернативный интерфейс над таблицами системы. В силу того, что зачастую в современных системах база рассматривается как набор таблиц, хранящих данные и не более, а nexus работает с хранимыми процедурами, то на сегодняшний момент nexus — это единственная технология внедрения в любую учетную систему на базе mssql. Это я проделывал с базами DocsVision and 1C. возврат к Nexus обязательно произойдет., имхо.