|
Я писал на pl/sql достаточно много. Начиная с версии
Oracle 7. Но оформить свою библиотеку удалось не так давно.
Ее гордое :))) название BillProf. Это от того, что
собралась она в момент разработки
билинговой системы. Вся библиотека, это
около 500K исходных текстов, тестов на pl/sql и
немного java. Тестировалась и проверялась она
на версии Oracle 8.1.7 на Sun Solaris. Пользоваться
ей полностью или частями можно по принципу
"AS IS". Очень приветствуется упоминание
автора :)).
Одним из важных собственных достижений я
считаю технологию разработки кода. Она
включает в себя как генераторы кода, так и
принципы наименования и размещения
составных частей. Эти принципы описываются
ниже.
Общие принципы
Принципы разработки собственно
биллинговой системы собраны в виде архива
документов. В этом архиве перечислены
сущности планировавшейся системы, ее
основные функции. Составлением формальных
документов занималась команда в составе:
Валера Юхно (vpu), Ян Дербенко(yan), Андрей Колесников (mantis),
Дима Козлов (dimka) и я (dsvolk). И права на эту
документацию принадлежат Элвис-Телеком.
По результатам обсуждений и собственного
опыта я разрабатывал серверную часть
системы. При поддержки вышеперечисленной
команды, естественно. Модули типа BAPI
могли быть разработаны только при их
участии.
Java модули, кроме jakarta-ora написаны Сергеем
Шарана (Модуль). Jakarta-ora - Apache
Jakarta Project.
Некоторые процедуры написаны по идеям Тоm
Kyte.
При разработке бизнес-модулей активно
использовался Oracle Designer 6.4 и его table API. Т.е.
физическая модель данных проектировалась в
Designer, откуда с помощью Server Generator получался
код таблиц, триггеров и пакетов Table API. Принципы
построения кода можно найти здесь.
Вся библиотека разбита на модули, каждый
модуль имеет 2(3)-х буквенное обозначение.
Часть модулей относиться к Business API (BAPI) и не
отделима от структуры данных, другая часть,
напротив носит вспомогательный характер и
легко может быть отделена. К сожалению не
все модули находятся в одинаковой степени
завершенности :((.
В
таблице ниже я даю расшифровку модулей и к какому типу они
относятся.
| Наименование |
Назначение |
Тип |
| CG |
Пакет cg$errors из стандартной поставки
Designer |
Вспомогательный |
| CM |
Context Managment. Этот модуль отвечает за
обработку ошибок (cm_error),
хранение всех констант библиотеки(cm_const),
поддержку разных языков сообщений (cm_utils).
Благодаря ему в коде библиотеки нет
зашитых сообщений, все они храняться в
структурах CM. |
Вспомогательный |
| CRD |
CaRD. Подсистема работы с интернет-картами.
Учет производства карт, передача их на
реализацию, активизация и т.п.
Постановка задачи - mantis. |
BAPI |
| DNS |
DNS. Хранение информации о DNS. |
BAPI |
| FM |
Finance and Marketing. База клиентов, актов
и счетов-фактуры. |
BAPI |
| GL |
General Ledger. Счета, остатки, проводки. |
BAPI |
| RG |
Registry. Возможность хранить
произвольные типы данных. Этот модуль
нужен как вспомогательный модулю IS. |
Вспомогательный |
| SL |
Standart Library. Содержит пакеты
отправки почты из Oracle sl_mail, работы с
тектовыми файловыми шаблоными (sl_template),
средство создание логов работы процедур
(sl_log), пакет работы со стандартными
регулярными выражениями - regexp (sl_regexp),
утилиты работы со строками (sl_str), набор
полезных разработчику подпрограмм (sl_utils) |
Вспомогательный |
| SS |
Security System. Набор и логика процедур
описана в документе Security Implimetation.doc |
Вспомогательный |
| IS |
Internet Services. Содержат BAPI для
работы со всеми типами интернет-услуг |
BAPI |
На файловой системе выдерживаются
следующие соглашения: в корне проекта есть
поддиректории
| PLSQL |
Исходные коды системы |
| DBA |
Утилиты для генерации исходного кода |
| WWW |
Файлы предназначенные для просмотра
через WEB |
Раздел PLSQL
В директории PLSQL расположены модули
библиотеки. В директории каждого
модуля есть следующие поддиректории:
| PLS |
Исходный код на pl/sql.
Файлы pks - заголовки пакетов, pkb - тела
пакетов. fnc - функции. typ - типы |
| SETUP |
Необходимые действия для установки
пакета |
| TAB |
Код создания объектов, которые не
попали в репозиторий Designer |
| TEST |
Тесты данного модуля |
| JAVA |
Исходный код на JAVA |
| SQL |
Внешние данные или вспомогательные SQL
скрипты |
Вот как в результате выглядит простой модуль
типа BAPI (код из файла fm_account.pkb):
-- Открыть позицию Плана
управления Ресурсами
PROCEDURE Ins (
p_fm_plan_string VARCHAR2 -- Строковое представление кода плана счетов
)
IS
row cg$fm_accounts.cg$row_type;
ind cg$fm_accounts.cg$ind_type;
exist_errors BOOLEAN := false;
BEGIN
row.fm_plan_code:= fm_account_plan.string2code(p_fm_plan_string);
--- Заполняем массив для TAPI
ind.fm_plan_code:=true;
-- Вызов Table API
cg$fm_accounts.ins(row, ind);
------ Вызываем TAPI
--
EXCEPTION WHEN cm_const.tapi_exception THEN
cm_error.ProcessError; --- Единый обработчик всех ошибок
END;
Хочется похвалиться обработчиком ошибок.
Он умеет, например, вместо сообщения о
нарушении уникального ключа, дать
сообщение которое Вы сопоставили с именем
первичного ключа. Он умеет вместо
наименований полей из БД сообщить их
русские пользовательские названия (вместо
account_no -> номер счета). Он умеет вместо
нарушения foreing key, сообщить что на самом деле
забыли завести. В общем, за внешней
простотой обработчика скрывается его мощь
:))
Чуть усложним задачу. Сделаем несколько
проверок. Код из файла fm_client.pkb
PROCEDURE upd_denorm ( row IN OUT cg$fm_clients.cg$row_type ,
ind IN OUT cg$fm_clients.cg$ind_type )
IS
exist_errors BOOLEAN := false;
v_tab sl_str.plsql_table;
BEGIN
-- Проверим что имя действительно
введено на русском
IF (cm_utils.checkdomain ('cl_first_name', row.cl_first_name, 'RUSNAME' ) <> 1 ) THEN
exist_errors := true;
END IF;
-- Проверим что имя введено на английском
IF (cm_utils.checkdomain ('cl_international_name', row.cl_international_name, 'ENGNAME' ) <> 1 ) THEN
exist_errors := true;
END IF;
-- Сформулируем короткое имя
IF (row.cl_short_name IS NULL ) THEN
IF ( row.cl_tax_type = 'PERS' ) THEN
v_tab.DELETE;
sl_str.intable(v_tab, row.cl_first_name);
sl_str.intable (v_tab, row.cl_last_name);
row.cl_short_name := sl_str.table2string (v_tab, ' ') ;
ELSE
row.cl_short_name := row.cl_last_name;
END IF;
ind.cl_short_name := true;
END IF;
--
IF ( exist_errors ) THEN --- Эту переменную мы
сами установили в true чтобы отметить
-- что в стеке есть ошибки
cg$errors.raise_failure;
END IF;
--
EXCEPTION WHEN cm_const.tapi_exception THEN
cm_error.ProcessError;
END;
В этом примере видно, что можно
накапливать ошибки в стек. Т.е. пользователь
получает ошибки не по одной, а сразу все что
ему нужно исправить. И не "field cannot be NULL",
а "поле наименование клиента должно быть
заполнено". Естественно, что соответствие
вы должны заполнить в модуле CM. Пример см. cm/setup/mrus.sql
И наконец "возбуждение" пользовательских
ошибок (код из файла fm_order.pkb):
-- Удаляет деталь счета
--
PROCEDURE DelDetail (
p_ord_det_id fm_order_details.ord_det_id%TYPE -- Первичный ключ детали счет
)
IS
row cg$fm_order_details.cg$row_type;
ind cg$fm_order_details.cg$ind_type;
pk cg$fm_order_details.cg$pk_type;
exist_errors BOOLEAN := false;
--
v_ord_status fm_orders.ord_status%TYPE; -- Статус счета
BEGIN
row.ord_det_id:=p_ord_det_id; ind.ord_det_id:=true;
cg$fm_order_details.slct(row );
-- Получим статус счета
SELECT o.ord_status
INTO v_ord_status
FROM fm_orders o
WHERE o.ord_id = row.ord_id;
-- Можно редактировать открытый или частично оплаченный счет
IF (v_ord_status NOT IN ('OPEN', 'PARTPAID') ) THEN
cm_error.raisebapierror ('WRONG_ORDER_STATUS', v_ord_status );
END IF;
-- Если эта позиция была оплачена
IF (row.closed_amount > 0 ) THEN
cm_error.raisebapierror ('ORDER_DET_PAID'); -- Возбуждаем
пользовательское сообщение
END IF;
pk.ord_det_id := p_ord_det_id;
cg$fm_order_details.del (pk);
-- Теперь надо общую сумму счета пересчитать
update_summary (row, ind, 'DEL' );
--
EXCEPTION WHEN cm_const.tapi_exception THEN
cm_error.ProcessError;
END;
Вы можете видеть, что реальное сообщение
скрывается за идентификатором ORDER_DET_PAID.
В процедуре UpdCorporateAccount из файла fm_client.pkb Вы
можете видеть процедуры проверки прав
доступа и журналирования.
----------------------------------------------------------------------------
-- Обновляем счета организации
--
PROCEDURE UpdCorporateAccount
(
p_ca_id fm_corporate_accounts.ca_id%TYPE, -- Внутренний ключ
p_ca_bank_name fm_corporate_accounts.ca_bank_name%TYPE, -- Наименование банка
p_ca_settlement_acccount fm_corporate_accounts.ca_settlement_acccount%TYPE, -- Расчетный счет организации
p_ca_correspondent_account fm_corporate_accounts.ca_correspondent_account%TYPE, -- корреспонденский счет
p_ca_bik fm_corporate_accounts.ca_bik%TYPE, -- Бик банка
p_notes VARCHAR2 -- Объяснение причины изменения
)
IS
row cg$fm_corporate_accounts.cg$row_type;
ind cg$fm_corporate_accounts.cg$ind_type;
exist_errors BOOLEAN := false;
BEGIN
row.ca_id:=p_ca_id; ind.ca_id:=true;
cg$fm_corporate_accounts.lck (row, ind);
row.ca_bank_name:=p_ca_bank_name; ind.ca_bank_name:=true;
row.ca_settlement_acccount:=p_ca_settlement_acccount; ind.ca_settlement_acccount:=true;
row.ca_correspondent_account:=p_ca_correspondent_account; ind.ca_correspondent_account:=true;
row.ca_bik:=p_ca_bik; ind.ca_bik:=true;
-- Security Check for staffs
ss_auth (row.branch, row.access_group, row.access_level)
-- Сохранить в журнальной таблице
row.jn_notes := p_notes;
cg$fm_corporate_accounts.insert_jn (row, 'UPD');
-- Изменить значения
cg$fm_corporate_accounts.upd(row, ind);
--
IF ( exist_errors ) THEN
cg$errors.raise_failure;
END IF;
--
EXCEPTION WHEN cm_const.tapi_exception THEN
cm_error.ProcessError;
END;
Раздел DBA
Здесь содержатся вспомогательные скрипты,
в том числе генераторы для модулей BAPI. Эти
скрипты имеют префикс gen_*. Так например,
gen_upd.sql генерит пару функций Get/Set для поля
нужной вам сущности с учетом прав доступа,
журналирования, обработки ошибок. Т.е. всего,
что мы рассмотрели Выше. Вам остается
только вставить этот код к себе и добавить
специфические проверки (к примеру new.salary
<= old.salary * 1.5) , если этого требует бизнес.
Заключение
Я надеюсь, что в таком кратком виде, мне
удалось показать, что предложенные мной
идеи формализуют процесс разработки,
упрощают и ускоряют его. А также позволяют
делать код, содержащий меньше ошибок.
P.S.
Огромное влияние на идеи и принципы,
заложенные в этой библиотеке, оказала
команда разработчиков, собравшаяся в одно
время в Zenon N.S.P: Федор Краснов (теория
биллинга, pl/sql, perl) , Андрей Кулага (pl/sql,
тарификатор) , Алексей Ващенко (Designer, pl/sql,
бухгалтерия), Александр Диментов (теория
биллинга, pl/sql, support) , Михаил Кулаков (perl,
сервера предоставления услуг), Мария
Соболева(perl, интерфейс). Мне было очень
приятно работать с ними. Я знаю, что многие
из них по прежнему продолжают трудиться на
биллинговыми системами и желаю им удачи в
этом нелегком деле.
|