Страница 1 из 3

Personal Pascal Peas

СообщениеДобавлено: 14.06.2012 23:03:04
Brainenjii
Публикую свой кодогенератор и ищу сочувствующих ^_^
Суть такова - по json описанию классовой модели генерируется набор исходников для "транзакционной" модели работы с объектами. Подробнее - здесь.
На конкретном примере:
{
  "kind": 0,
  "version": 0,
  "peas": [{
    "id": 100,
    "caption": "PeaValue",
    "pluralCaption": "PeaValues",
    "sqlCaption": "PEA_VALUE",
    "kind": "plain",
    "properties": [{
      "caption": "Caption",
      "sqlCaption": "CAPTION",
      "kind": "string"
    }, {
      "caption": "IntValue",
      "sqlCaption": "INT_VALUE",
      "kind": "integer"
    }, {
      "caption": "Caption",
      "pluralCaption": "Captions",
      "sqlCaption": "CAPTIONS",
      "kind": "strings"
    }, {
      "caption": "MyIntValue",
      "pluralCaption": "MyIntValues",
      "sqlCaption": "MY_INT_VALUES",
      "kind": "integers"
    }]
  }, {
    "id": 105,
    "caption": "PeaValueHolder",
    "pluralCaption": "PeaValueHolder",
    "sqlCaption": "PEA_VALUE_HOLDER",
    "kind": "holder",
    "pea": "PeaValue",
    "properties": [{
      "caption": "SomeCaption",
      "sqlCaption": "SOME_CAPTION",
      "kind": "string"
    },{
      "caption": "SomeInt",
      "sqlCaption": "SOME_INT",
      "kind": "integer"
    }]
  }, {
    "id": 110,
    "caption": "PeaContainer",
    "pluralCaption": "PeaContainers",
    "sqlCaption": "PEA_CONTAINER",
    "kind": "plain",
    "properties": [{
      "caption": "Caption",
      "sqlCaption": "CAPTION",
      "kind": "string"
    }, {
      "caption": "Link",
      "sqlCaption": "LINK",
      "kind": "pea",
      "pea": "PeaValue"
    },{
      "caption": "HugePeaValue",
      "pluralCaption": "HugePeaValues",
      "sqlCaption": "HUGE_PEA_VALUES",
      "kind": "holders",
      "pea": "PeaValueHolder"      
    }, {
      "caption": "PeaValueFirst",
      "pluralCaption": "PeaValuesFirst",
      "sqlCaption": "PEA_VALUE_FIRST",
      "kind": "peas",
      "pea": "PeaValue"
    }, {
      "caption": "PeaValueLast",
      "pluralCaption": "PeaValuesLast",
      "sqlCaption": "PEA_VALUE_LAST",
      "kind": "peas",
      "pea": "PeaValue"
    }]
  }]
}это то самое json описание. После применения кодогенератора появятся три исходника
BPeaContainerUnit
BPeaValueHolderUnit
BPeaValueUnit
И SQL скрипт (объединил в один файл)
И теперь то, ради чего все затевалось:
Код: Выделить всё
program project1;

{$mode objfpc}{$H+}

uses
  {$IFDEF UNIX}cthreads,{$ENDIF}
  BQueryUnit, BPeaValueUnit
  { you can add units after this };

Var
  aManager, aOtherManager: BPeaValuesManagerClass;
  aPeaValue, aBuffer: BPeaValueClass;
begin
  // Вызывается лишь единожды при старте программы - регистрирует базу
  DBManager.AddDatabase(DB_FB, 'localhost', 'dummy', 'SYSDBA', 'masterkey');
  DBManager.Connect;
  aManager := BPeaValuesManagerClass.Build;
  aOtherManager := BPeaValuesManagerClass.Build;
  Try
    aPeaValue := aManager.AddObject;
    aPeaValue.Initialize('Text', 100);

    aBuffer := aOtherManager.GetObject(aPeaValue.ID);
    If aBuffer = nil Then
      WriteLn('Второй менеджер ничего не знает об первом объекте');
    aManager.Commit;
    aBuffer := aOtherManager.GetObject(aPeaValue.ID);
    If aBuffer = nil Then
      WriteLn('Теперь уже знает')
    Else
      WriteLn(aBuffer.Caption);
  Finally
    aManager.Burn;
    aOtherManager.Burn;
  End;
end.

В таблице PEAVALUE появится соответствующая запись ^_^

Re: Personal Pascal Peas

СообщениеДобавлено: 17.06.2012 14:16:06
Brainenjii
Ех... Если есть какие-то вопросы - буду рад услышать ^_^
Немного описания ^_^
PPP - это генератор объектной модели для реляционных баз данных (пока только firebird). Задача - сконцентрировать работу с Базой Данных в методах Save и Load, хотя цель полностью абстрагироваться от базы не преследуется (но для многих проектов будет достаточно возможностях, предоставляемых объектной моделью). На данный момент генерируемый код не умеет получать объекты из базы по запросу, т.е. загружает сразу все объекты определённого типа, что довольно сильно ограничивает его применение, однако его можно использовать как основу для реализации данной задачи. Забегая вперёд, загрузку по запросу планируется указывать для объектов свойством onDemand: true.
Каждый объект модели называется горошиной (pea), в пику джавовским бобам ^_^
Весь код генерируется по описанию peas.json в формате, похожем на JSON. Свойства:
  • version - версия описания. Пока не используется, но в последствии будет сохраняться в комментариях к сгенерированным исходникам.
  • projectName - название проекта, для которых генерируется исходники. Пока тоже не используется, но будет нужен для формирования загрузчика и т.п.
  • podKind - вид базы данных. Пока поддерживается только firebird, не сложно реализовать и для других СУБД (дополнить методы загрузки и сохранения).
  • peas - массив горошин.
Каждая горошина - это JS literal-object. Его свойства:
  • id - уникальный номер класса. Доступ к нему можно получить из свойства ClassID
  • caption/pluralCaption - название класса и название во множественном числе. В sql скрипте создаётся одноименное название таблицы в верхнем регистре.
  • kind - тип горошины. Возможные значения:
    • plain - обычный объект с уникальным идентификатором (ID);
    • tree - объект с иерархическими связями - ссылкой на родителя, следующий и предыдущий объект;
    • holder - "держатель". Особая горошина, не имеет собственного ID. Использует ID держимого объекта. Используется, чтобы добавить какие-то параметры при использовании в качестве параметра к другой горошине
    • simple - не реализовано. Простая горошина. ID не имеет в принципе. Используется, чтобы миновать механизм менеджеров.
  • onDemand - не реализовано. Будет использоваться для определения - загружаются ли все соответствующие объекты сразу, или по запросу.
  • properties - массив свойств горошины.
    • caption/pluralCaption/sqlCaption - наименование свойства (во множественном числе и для sql скриптов)
    • kind - тип свойства. Могут быть следующие типы:
      • string,integer,double,date,datetime,time,boolean - элементарные типы
      • strings,integers,doubles,datetimes,booleans - списки элементарных типов. Пока под каждое такое поле заводится отдельная таблица. Возможно есть смысл объединить в одну, или сделать это опцией в отдельное поле к свойству
      • pea - ссылка на горошину. Обязательно указать поле pea у самого свойства.
      • peas - список горошин. Обязательно указать поле pea у самого свойства.
      • holders - список "держателей" горошин с соответствующими свойствами. Создаёт набор таблиц. Обязательно указать поле pea у самого свойства, причем тип горошины должен быть holder
    • onDemand - см. соответствующее свойство самой горошины
    • pea - ссылка на горошину, представляющее свойство
Вот как-то так ^_^

Re: Personal Pascal Peas

СообщениеДобавлено: 18.06.2012 11:59:01
vada
Прелестно! Прелестно! Идея очень понравилась. Буду знакомиться.
ЗЫ. С горошиной это вы здорово! Выборку можно стручком назвать :)

Re: Personal Pascal Peas

СообщениеДобавлено: 18.06.2012 20:28:07
B4rr4cuda
Очень любопытная идея. Надо будет пощупать.

Re: Personal Pascal Peas

СообщениеДобавлено: 18.06.2012 21:59:37
Brainenjii
Спасибо! ^_^
Несколько дополнений по держателям (теперь свойством держателя может быть другой держатель и т.д.) и начата работа на Controller'ами. Перед ними я вижу следующие задачи:
  • 1. Сериализация/десериализация горошин в формат JSON (нравится он мне ^_^)
  • 2. Организовать работу с менеджерами, принимая в параметры только элементарные типы (строки, числа и т.д.)
Поскольку последнее время я увлёкся веб-интерфейсами, вопрос обмена данными с представлением только через строки (не знаю, как передать запись или, того краше, объект через AJAX) для меня довольно важен. Плюс, такие контроллеры - задел на "сетевую прозрачность" горошин ^_^ Параллельно с реальным контроллером будет создаваться болванка контроллера, вызовы методов которой будут отправляться по сети настоящему контроллеру, который будет выполнять реальные действия и возвращать болванке результат (все теми же примитивными типами). По-моему, вполне реально ^_^

Re: Personal Pascal Peas

СообщениеДобавлено: 27.06.2012 23:57:23
Brainenjii
Ещё одна порция обновлений. Продолжена работа над парсингом JSON (десериализацией).
Напомню про механизм управляющих (*ManagerClass) и управляемых классов. Для каждой горошины строится 3 класса:
  • собственно сам класс с данными. Название строится как B<Caption>Class. В дальнейшем просто горошина или управляемый класс.
  • класс, осуществляющий "транзакционный" режим работы. Название строится как B<PluralCaption>ManagerClass. В дальнейшем менеджер или управляющий класс. Может быть есть смысл называть "Стручком" ^_^
  • класс, оборачивающий работу с менеджером в вызовы методов, параметры которых представлены только простыми типами. Я называю его контроллером и потому название строится как B<PluralCaption>ControllerClass.
Теперь обо всём этом подробнее ^_^ И чтобы было проще - попробуем разобраться на примере (а заодно может для маман так и доделаю плёвую программку по расчету рейтинга :-D).
Задача такая. Есть школа, а значит учителя, классы, ученики, контрольные, оценки и т.д. Нужно, чтобы преподаватель мог назначить контрольные, которым назначается максимальное количество баллов и "значимость" этой контрольной в учебном году. Затем, проставив сколько ученик набрал баллов из максимально возможного за контрольную, учитель возвращается результат - кто какую оценку получил, и как это повлияло на оценку ученика за год/четверть. Итак, начнём формировать наше JSON описание классовой модели:
Код: Выделить всё
{
  "version": 0,
  "projectName": "School",
  "podKind": "firebird",
  "peas": [{
    "id": 100,
    "caption": "Pupil",
    "pluralCaption": "Pupils",
    "kind": "plain",
    "properties": [{
      "caption": "FirstName",
      "sqlCaption": "FIRSTNAME",
      "kind": "string"
    }, {
      "caption": "MiddleName",
      "sqlCaption": "MIDDLENAME",
      "kind": "string"
    }, {
      "caption": "LastName",
      "sqlCaption": "LASTNAME",
      "kind": "string"
    }]
  }, {
    "id": 110,
    "caption": "Grade",
    "pluralCaption": "Grades",
    "kind": "plain",
    "properties": [{
      "caption": "Parallel",
      "sqlCaption": "PARALLEL",
      "kind": "integer"
    },{
      "caption": "Literal",
      "sqlCaption": "LITERAL",
      "kind": "string"
    },{
      "caption": "Pupil",
      "pluralCaption": "Pupils",
      "sqlCaption": "PUPILS",
      "kind": "peas",
      "pea": "Pupil"
    }]
  }, {
    "id": 120,
    "caption": "Teacher",
    "pluralCaption": "Teacher",
    "kind": "plain",
    "properties": [{
      "caption": "FirstName",
      "sqlCaption": "FIRSTNAME",
      "kind": "string"
    }, {
      "caption": "MiddleName",
      "sqlCaption": "MIDDLENAME",
      "kind": "string"
    }, {
      "caption": "LastName",
      "sqlCaption": "LASTNAME",
      "kind": "string"
    },{
      "caption": "Grade",
      "pluralCaption": "Grades",
      "sqlCaption": "GRADES",
      "kind": "peas",
      "pea": "Grade"
    }]
  }]
}

Начну с менеджеров. Эти классы упорядочивают работу с горошинами. Каждый менеджер содержит 2 списка горошин - основной (объявлен с ключевым словом Static) и частный - создаётся вместе с экземпляром менеджера.
В несколько отдалённом будущем планируется в конструкторе менеджера указывать флаг ReadOnly, при котором частный список создаваться не будет и вообще многие проверки будут неактуальными - некоторый запас "оптимизации" ^_^
"Транзакционность" работы с объектами заключается в следующем - новые/удаляемые/редактируемые горошины не сразу создаются/удаляются/редактируются в основном списке, а через вызовы соответствующих методов менеджеров, которые будут создавать копии горошин, помещаемые в частный список менеджера. И в общий список изменения попадают только после вызова метода Commit. Так же, метод Commit инициирует запись изменений в базу данных.
В более близком будущем планируется делать "двуфазный" коммит - когда сам Commit подготавливает все изменения для записи в статический список горошин, а запросы к базе данных выполняются, но без вызова Commit у транзакции до тех пор пока не будет вызван метод FinalCommit (условно). Это потребуется, когда одна горошина будет вызывать изменений многих других.
Несколько основных методов менеджеров:
  • AddObject - регистрирует новый объект в частном списке горошин текущего экземпляра менеджера с флагом "Новый"
  • ChangeObject - создаёт клон объекта и помещает её в частный список и ставит ей флаг "Изменён"
  • DeleteObject - вызывает ChangeObject, а затем помечает клон на удаление
  • Commit - пробегается по частному списку, вызывает у каждого объекта в нём метод Save и если всё хорошо - отправляет изменения в основной список горошин.
  • Load - загружает пока все горошины из базы
Таким образом, чтобы завести нового ученика в базу нам потребуется:
Код: Выделить всё
Var
  aManager: BPupilsManagerClass; // класс менеджера
  aPupil: BPupilClass; // собственно сам класс ученика
Begin
  // 1. Создаём менеджера учеников
  aManager := BPupilsManagerClass.Build;
  // 2. Регистрируем нового ученика.
  aPupil := aManager.AddObject;
  // 3. Заполняем поля объекта BPupilClass (об этом в следующий раз ^_^)
  aPupil.Initilize('Иван', 'Иванович', 'Иванов');
  // 4. Вызываем Commit
  aManager.Commit;
  // 5. Подчищаем за собой - уничтожаем менеджер
  aManager.Burn;
End;

Всё, ученик создан (если, конечно, заменили дефолтную реализацию валидации горошины Pupic ^_^).

Re: Personal Pascal Peas

СообщениеДобавлено: 28.06.2012 01:35:50
Kitayets
это всё очень круто, но можно мне немного объяснить - для чего это может понадобится? какой-нибудь пример архитектуры приложения + задачу которую оно должно решать, так чтобы потребовался данный механизм. Можно и ссылку на ресурс/ книгу с описанием и реализацией таких задач.

Re: Personal Pascal Peas

СообщениеДобавлено: 28.06.2012 08:19:10
Brainenjii
В общем случае, это может понадобиться, когда вы хотите абстрагироваться от БД, как обычного носителя данных (до тех пор, пока не потребуются сложные запросы с соединениями и т.п.).
По поводу архитектуры - наибольший плюс должен быть при трёхзвенке. Пример - как оно будет - попытаюсь разобрать на примере вышеуказанного проекта.
Что касается задачи - она одна ^_^ Иметь возможность изменять объект так, чтобы все изменения могли проводиться/откатываться без задействования СУБД. В некотором плане я пытаюсь повторить механизм Commit/Rollback СУБД на уровне объектной модели. Зачем - ответ выше - чтобы абстрагироваться от БД

Re: Personal Pascal Peas

СообщениеДобавлено: 28.06.2012 13:57:20
alexs
Brainenjii
А почему вы не хотите работать с БД напрямую?
Зачем изобретать свой велосипед, вместо того чтобы использовтаь все 100% возможностей, которые вам даст СУБД?

Re: Personal Pascal Peas

СообщениеДобавлено: 28.06.2012 14:36:45
Brainenjii
Хм. Мешать pascal и SQL код мне просто не нравится ^_^ Внутренне ^_^ Работать с датасетами/запросами напрямую - тоже.
Обернуть строку выборки из запроса в объект и затем работать с ним - это же просто удобнее.
Но чтобы обернуть запрос в объект - нужно указывать все поля, типы, списки... Каждое изменение структуры БД - муторное изменение кода во всех местах где оно встречается...
Можно было, конечно, пойти путём замены существующих менеджеров на саму СУБД и все механизмы транзакции оставить ей, а кодогенератор только бы занимался вопросами сохранения/чтения и структуры БД. Это, по сути, и будет то самое OnDemand свойство, о котором упоминал во втором посте. Механизм менеджеров создавался, чтобы обеспечить максимально быстрый доступ к объектам, доступ к которым нужен постоянно.
Как-то так ^_^

Re: Personal Pascal Peas

СообщениеДобавлено: 28.06.2012 18:02:16
alexs
Brainenjii писал(а): Каждое изменение структуры БД - муторное изменение кода во всех местах где оно встречается...

Brainenjii писал(а):Работать с датасетами/запросами напрямую - тоже.

Brainenjii писал(а):Обернуть строку выборки из запроса в объект и затем работать с ним - это же просто удобнее.

Вот не понял - как это?
Ваши самописные обёртки при изменении структур в БД позволят не менять логику в клиентской части? НЕ ВЕРЮ!
Просто написание программы начните с того, с чего надо - проектирование данны, которая она будет обрабатывать.
А уже потом начинайте кодить.
При грамотно описанной предметной области и хорошо проведённой декомпизиции данных ситуации с кардинальным переписываем кода не будет.
Ну а на уровне кодинга DataSet сам является хорошим представлением данных в объектную модель паскаля.

Re: Personal Pascal Peas

СообщениеДобавлено: 28.06.2012 18:41:51
Brainenjii
Наши самописные обёртки позволяют "забыть" о том что есть какие-то базы данных, в них какие-то поля и т.д. Править остаётся, действительно, только логику в клиентской части.
Предусмотреть всё на этапе проектирования нельзя. Самописные (и самопереписывающиеся) обертки позволяют значительно упростить внесение корректировок в данные. Честно.

Re: Personal Pascal Peas

СообщениеДобавлено: 28.06.2012 19:41:17
alexs
Brainenjii
Ваша позиция в корне не верна.
В первую очередь надо проектировать данные. А уж логика их обработки сама обрисуется.
Я вот в начале года столкнулся с таким горе-конструктором.
Вся логика на клиенте.
В СУБД - ОДНА! таблица в которой всё свалено.
На простейший вопрос - поменять флажок по умолчанию для всех пользователей разроботчики ответили - вот есть платформа - пишите код для этого.
Как вы считаете - это верное решение?
Я уж не говорю о прочих прелестях этого "тонкого" клиента (при установке на клиенте магическим образом оказывае 300 метров огрызков от .net всех версий и прочей гадости..)
PS
Почему все боятся работать с СУБД? Зачем нужен промежуточный слой переработки данных? Неужели так трудно изучить 4-ре команды?

Re: Personal Pascal Peas

СообщениеДобавлено: 28.06.2012 23:42:27
Kemper
alexs писал(а):В первую очередь надо проектировать данные. А уж логика их обработки сама обрисуется.

Браво!

Re: Personal Pascal Peas

СообщениеДобавлено: 29.06.2012 00:20:33
B4rr4cuda
Brainenjii, а я поддержу тебя.
Так получилось, что мне пришлось около года сопровождать проект какого-то гаврика, который налепил смесь логики и работы с бд в один большой кусок удобрения.

Так что на прослойки между логикой и БД теперь смотрю с большим одобрением...