Книга: Дефрагментация мозга. Софтостроение изнутри

Лампа, полная джиннов

Метафора системы достаточно проста: хочешь генерировать код компонента или слоя – попроси об этом соответствующего «джинна» в форме стандартного «заклинания». Джинны, как им и положено, живут в лампе.

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


Рис. 19. Общая схема работы с «лампой» и «джиннами»

Модель в виде XML-файлов поступает на вход «заклинателю» – входящей в состав пакета консольной утилите. Производятся проверки непротиворечивости модели, выдающие ошибки либо предупреждения разной степени важности. Во время анализа модель также преобразуется во внутренний формат в виде множества объектов с открытыми интерфейсами доступа.

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

Обработав модель в соответствии с конфигурацией проекта, джинн выдаёт готовый к компиляции в среде разработки код. Для слоя хранения данных кроме генерации специфичных для СУБД SQL-скриптов производится их прогон на заданном сервере разработки.

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

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

Пример модели в Genie Lamp

<Type name="TEntityId" baseType="int" />

<Enumeration name="Granularity">

<Doc><Label lang="ru">Грануляция учётного периода</Label></Doc>

<Item name="Day" value="0">

<Doc><Label lang="ru">День</Label></Doc>

</Item>

<Item name="Month" value="1" default="true">

<Doc><Label lang="ru">Месяц</Label></Doc>

</Item>

<Item name="Year" value="2">

<Doc><Label lang="ru">Год</Label></Doc>

</Item>

</Enumeration>

<Entity name="FiscalYear">

<Doc><Label lang="ru">Финансовый год</Label></Doc>

<Attribute name="Id" type="TEntityId" primaryid="true" autoincrement="true" />

<Attribute name="Name" type="TCaption" uniqueid="true">

<Doc><Label lang="ru">Обозначение года</Label></Doc>

</Attribute>

<Attribute name="Granularity" type="Granularity">

<Doc><Label lang="ru">Грануляция периодов</Label></Doc>

</Attribute>

<Attribute name="FromDate" type="date">

<Doc><Label lang="ru">Дата начала</Label></Doc>

</Attribute>

<Attribute name="ToDate" type="date">

<Doc><Label lang="ru">Дата окончания</Label></Doc>

</Attribute>

<Attribute name="Closed" type="boolean" default="false">

<Doc><Label lang="ru">Год закрыт?</Label></Doc>

</Attribute>

<Attribute name="GranularityName" type="string" persisted="false">

<Doc>

<Text lang="ru">Возвращает локализованое название грануляции</Text>

</Doc>

</Attribute>

<Operation name="CreatePeriods" access="public">

<Doc>

<Text lang="ru">

Создает периоды финансового года

между датами начала и окончания

в соответствии с грануляцией. Например, для фин. года,

совпадающего с календарным, и помесячной грануляцией

будут созданы 12 месячных периодов

</Text>

</Doc>

<Returns type="void"/>

</Operation>

<Operation name="FindPeriodIdByDate" access="public">

<Doc>

<Text lang="ru">

Возвращает ID периода по заданной дате, "0" если не найден

</Text>

</Doc>

<Param name="periodDate" type="datetime"/>

<Returns type="TEntityId"/>

</Operation>

<Operation name="DeleteCascade" access="public">

<Returns type="void"/>

</Operation>

</Entity>

<Entity name="Period">

<Doc><Label lang="ru">Учётный период</Label></Doc>

<Attribute name="Id" type="TEntityId" primaryid="true" autoincrement="true" />

<UniqueId>

<Attribute name="FiscalYearId" type="TEntityId">

<Doc><Label lang="ru">ID финансового года</Label></Doc>

</Attribute>

<Attribute name="FromDate" type="date">

<Doc><Label lang="ru">Дата начала</Label></Doc>

</Attribute>

</UniqueId>

<UniqueId>

<OnAttribute name="FiscalYearId"/>

<Attribute name="PeriodNumber" type="smallint">

<Doc><Label lang="ru">Номер периода</Label></Doc>

</Attribute>

</UniqueId>

<Attribute name="ToDate" type="date">

<Doc><Label lang="ru">Дата окончания</Label></Doc>

</Attribute>

</Entity>

<Relation entity="Period" name="FiscalYear"

entity2="FiscalYear" name2="Periods"

cardinality="M:1">

<AttributeMatch attribute="FiscalYearId" attribute2="Id" />

</Relation>

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

• слои хранения будут развёрнуты на SQL Server или Oracle;

• слой домена под управлением NHibernate;

• слой веб-служб на базе ServiceStack (вместо WCF, имеющего под Mono/Linux ограничения).

Пример конфигурации проекта в Genie Lamp

<! – Включаем файл(ы) модели в проект – >

<ImportModel fileName="MyModel.xml" />

<! – Будем использовать джинна SQL Server – >

<Genie name="SqlServer"

type="GenieLamp.Genies.SqlServer.SqlServerGenie"

assembly="GenieLamp.Genies.SqlServer"

active="false"

outDir="%PROJECT_DIR%/../SQL/SqlServer-%TARGET_VERSION%"

outFileName="%PROJECT_NAME%.sql"

updateDatabase="true"

targetVersion="2008">

<Param name="Database.Create" value="false" />

… Другие параметры "заклинания"

</Genie>

<! – Будем использовать джинна Oracle – >

<Genie name="OracleDb"

type="GenieLamp.Genies.Oracle.OracleGenie"

assembly="GenieLamp.Genies.Oracle"

active="true"

outDir="%PROJECT_DIR%/../SQL/Oracle-%TARGET_VERSION%"

outFileName="%PROJECT_NAME%.sql"

outFileEncoding="iso-8859-1"

updateDatabase="false"

targetVersion="10g">

<Param name="UniqueIndexConstraint" value="true" />

</Genie>

<! – Будем использовать джинна NHibernate для генерации домена – >

<Genie name="NHibernate"

type="GenieLamp.Genies.NHibernate.NHibernateGenie"

assembly="GenieLamp.Genies.NHibernate"

active="true"

outDir="%PROJECT_DIR%/../Domain"

outFileName="%PROJECT_NAME%.Domain.cs"

targetVersion="*">

<Param name="TargetAssemblyName" value="Company.Business.%PROJECT_NAME%.

Domain" />

</Genie>

<! – Будем использовать первого джинна ServiceStack

для генерации интерфейсов к веб-службам – >

<Genie name="ServiceStack Services Interfaces"

type="GenieLamp.Genies.ServicesLayer.ServiceStack.ServicesInterfacesGenie"

assembly="GenieLamp.Genies.ServicesLayer"

active="true"

outDir="%PROJECT_DIR%/../Services.Interfaces"

targetVersion

="*">

</Genie>

<! – Будем использовать второго джинна ServiceStack

для генерации собственно веб-служб – >

<Genie name="ServiceStack Services"

type="GenieLamp.Genies.ServicesLayer.ServiceStack.ServicesGenie"

assembly="GenieLamp.Genies.ServicesLayer"

active="true"

outDir="%PROJECT_DIR%/../Services"

targetVersion="*">

</Genie>

<Configuration>

<! – Конфигурация слоя хранения данных – >

<Layer name="Persistence">

<NamingConvention style="uppercase" maxLength="30">

<Param name="PrimaryKey.ColumnTemplate" value="NI%TABLE%" />

<Param name="PrimaryKey.ConstraintTemplate" value="PK_%TABLE%" />

… Другие шаблоны именований

</NamingConvention>

<Param name="ForeignKey.CreateIndex" value="true" />

<Param name="BooleanValues" value="YesNo"/>

</Layer>

<! – Конфигурация слоя домена – >

<Layer name="Domain">

<Param name="BaseNamespace" value="Company.Business.%PROJECT_NAME%" />

</Layer>

<! – Конфигурация слоя служб – >

<Layer name="Services">

<Param name="BaseNamespace" value="Company.Business.%PROJECT_NAME%" />

</Layer>

<! – Шаблон "Реестр объектов" – >

<Pattern name="Registry">

<Param name="Schema" value="Core" />

<Param name="PersistentSchema" value="CORE" />

<Param name="RegistryEntity.Name" value="EntityRegistry" />

<Param name="TypesEntity.Name" value="EntityType" />

<Param name="TypesEntity.PrimaryId.Type" value="smallint" />

<Param name="PrimaryId.Type" value="bigint" />

</Pattern>

<! – Шаблон "Версия состояния" для хранимых объектов – >

<Pattern name="StateVersion">

<Param name="Attribute.Name" value="Version" />

<Param name="Attribute.Type" value="int" />

</Pattern>

<! – Шаблон "Аудит" для минимального отслеживания изменений – >

<Pattern name="Audit" />

<! – Шаблон "Локализация" – >

<Pattern name="Localization" />

<! – Шаблон "Безопасность" для веб-служб – >

<Pattern name="Security" />

</Configuration>

В описании конфигурации джиннов видно, что его основу составляет сборка, один из классов которой, реализующий интерфейс IGenie, является точкой входа. Каждый джинн имеет как общие для всех параметры, например каталог для выходных файлов, так и специфичные, передаваемые через тег Param, описываемые в документации.

За джиннами следуют конфигурации слоёв. Если для домена и служб можно пока ограничиться спецификацией базового пространства имён, то для слоя хранения, особенно при поддержке более чем одной СУБД, необходимо указать дополнительные ограничения вроде максимальной длины имён.

Заключительная часть конфигурации представляет собой описания шаблонов. Но не тех, о которых идёт речь в книжке «банды четырёх», а о шаблонах реализации типовых задач уровня ядра и системных служб:

• Например, шаблон «Реестр объектов» добавляет к системе возможность ведения централизованного реестра всех создаваемых объектов. Реализован он как соответствующий класс и таблица, ссылка на которые добавляется ко всем другим классам (некоторые классы можно исключить через параметры шаблона).

• Шаблон «Версия состояния» является встроенной в NHibernate возможностью отслеживания конфликтов в многопользовательской среде. Например, если два пользователя изменяют один и тот же объект, то последний из них, сохранивший объект, получит исключение, оповещающее о том, что данные были изменены со времени последнего редактирования. Шаблон реализуется добавлением соответствующего атрибута номера версии ко всем классам.

• Шаблон «Аудит» в простейшем варианте является регистрацией для каждого хранимого объекта информации о времени его создания, последнем редактировании и авторе.

• Шаблон «Локализация» добавляет в генерируемый код возможность перевода сообщений в рамках технологии GNU gettext.

• Наконец, шаблон «Безопасность» в простейшем варианте ограничивает доступ к веб-службам через механизм аутентификации, логику которой необходимо реализовать в переопределяемом методе соответствующего класса. Например, обратиться к стороннему LDAP или непосредственно к базе данных с регистрационной информацией для проверки имени пользователя и хеша пароля.

Теперь, если запустить «заклинатель» с параметром файла конфигурации проекта и не будет обнаружено ошибок, на выходе мы получим инициализированные структуры баз данных и готовые к компиляции файлы. Рассмотрим их чуть подробнее.

Оглавление книги


Генерация: 0.099. Запросов К БД/Cache: 0 / 0
поделиться
Вверх Вниз