Этот пост о книге Мартина Фаулера, которую он анонсировал здесь, а знакомство с этим постом совпало по времени с моментом, когда нам пришлось разрабатывать свой DSL для описания SQL-запросов в программном коде на Delphi.
Ранее я уже поднимал тему внутренних DSL, т.е. DSL, в которых носителем синтаксиса является другой язык (Delphi в моём случае). С того момента мы существенно продвинулись - эта тема получила ряд интереснейших, на мой взгляд продолжений, но это уже другая история.
Хочу сказать о книге. Она удачна, хотя к стилю изложения Фаулера я испытываю отношение, которое сродни его отношению к использованию XML в качестве носителя синтаксиса DSL :-), очевидно, что автор знает, о чём он пишет.
В книге выделю часть IV "Вопросы создания внутренних DSL", в которой автор вводит понятие Построителя выражений, и рассматривает различные способы его настройки. Среди способов настройки автор довольно внятно описывают, в частности fluent-технику, которую называет "соединение методов в цепочки".
Думаю, что если бы была возможность ознакомиться с материалом год назад, мы вероятно не потратили бы две-три недели на изобретение того, что у него изложено довольно системно. Хотя... Как сказать... ;-)
Разумеется, в книге рассматриваются и другие подходы к разработке DSL.
Приятной особенностью книги является наличие разделов "Как это работает" и "Когда это использовать", что более чем востребовано при описании подходов.
В электронном виде книга стоит около 120 рублей, купил её я на books.ru, но думаю, приобрести несложно и в других местах...
Ранее я уже поднимал тему внутренних DSL, т.е. DSL, в которых носителем синтаксиса является другой язык (Delphi в моём случае). С того момента мы существенно продвинулись - эта тема получила ряд интереснейших, на мой взгляд продолжений, но это уже другая история.
Хотя, пожалуй приведу пример того, что мы имеем в настоящий момент:
ПоказатьСкрыть
В книге выделю часть IV "Вопросы создания внутренних DSL", в которой автор вводит понятие Построителя выражений, и рассматривает различные способы его настройки. Среди способов настройки автор довольно внятно описывают, в частности fluent-технику, которую называет "соединение методов в цепочки".
Думаю, что если бы была возможность ознакомиться с материалом год назад, мы вероятно не потратили бы две-три недели на изобретение того, что у него изложено довольно системно. Хотя... Как сказать... ;-)
Разумеется, в книге рассматриваются и другие подходы к разработке DSL.
Приятной особенностью книги является наличие разделов "Как это работает" и "Когда это использовать", что более чем востребовано при описании подходов.
В электронном виде книга стоит около 120 рублей, купил её я на books.ru, но думаю, приобрести несложно и в других местах...
вот расскажите пожалуйста, как вы ходите отладчиком по этим текучим интерфейсам?
ОтветитьУдалитьперечитал несколько раз код. непривычно, но круто.. наверное. особенно, если делать ставку на поддержку различных субд.
ОтветитьУдалитьа это пример из реального приложения?
«вот расскажите пожалуйста, как вы ходите отладчиком по этим текучим интерфейсам?»
ОтветитьУдалить-- Да как... Обычно... F7, F8, Toggle breakpoint... :-)
Но вообще-то, нечасто там приходится отладчиком ходить. Методы довольно простые.
Если уж "припечёт", то лучше breakpoint выставить.
«перечитал несколько раз код. непривычно, но круто.. наверное. особенно, если делать ставку на поддержку различных субд.
а это пример из реального приложения?»
-- Да, это пример из реального приложения. И это действительно СУБД-независимый код.
Действительно, поначалу непривычно, но не в большей степени, чем замыкания и разные трюки с интерфейсами.
Это всего лишь последовательный вызов методов, каждый из которых (обычно) возвращает ссылку на экземпляр класса, из которого он вызван.
ну про отладку я спросил потому, что точку останова нельзя пставить в середине выражения. например на строке
ОтветитьУдалить.Open
(ну допустим я хочу посмотреть текст генерируемого sql-выражения)
хотя понятно, что это не такая уж и проблема
про код из реального приложения - там где вы делаете
ОтветитьУдалитьcontact_data.Edit
и ниже определяете параметр StatusMaster
как я понимаю происходит выборка этого значения из БД на клиент.
Хотя на самом деле, для ускорения (и чтобы не делать лишний round-trip) можно написать на SQL так:
update lc_cont set statuscode = :statuscode, … , statusmaster =(select mastercode from users… )
ну т.е. вам есть куда ещё развиваться
Забавно... :-)
УдалитьТолько что посмотрел - на LC_CONT есть триггер уровня приложения.
Вот его код:
procedure hnd_LC_CONT_ApplyTriggers(P: Pointer; var Event: TComEvent);
var
DBC: TCustomConnection;
vOldStatus: Variant;
tabHIS_STAT: TDataSet;
trs: IDBTransaction;
begin
with TEvent_TriggerContainer_ApplyTriggers(Event.Parms^) do
if Operation = DC_MODIFY then
begin
{ если статус изменился сохранить историю }
with DataSet.FieldByName(nmStatusCode) do
begin
vOldStatus := OldValue;
if Value = vOldStatus then
Exit;
end;
{ добавить историю }
if vOldStatus <> 0 then
begin
DBC := ExtractConnection(DataSet);
trs := Transaction(DBC);
try
tabHIS_STAT := OpenTable(DBC, snHIS_STAT);
try
tabHIS_STAT.Insert;
tabHIS_STAT.FieldValues[nmContactCode] := DataSet.FieldValues[nmContactCode];
tabHIS_STAT.FieldValues[nmStatusCode] := vOldStatus;
tabHIS_STAT.FieldValues[nmStatusDate] := DataSet.FieldByName(nmStatusDate).OldValue;
tabHIS_STAT.FieldValues[nmStatusTime] := DataSet.FieldByName(nmStatusTime).OldValue;
tabHIS_STAT.FieldValues[nmStatusMaster] := DataSet.FieldByName(nmStatusMaster).OldValue;
tabHIS_STAT.Post;
finally
tabHIS_STAT.Free;
end;
PassEvent(Event);
trs.Commit;
except
trs.RollBack;
raise;
end;
end;
end;
end;
«Хотя на самом деле, для ускорения (и чтобы не делать лишний round-trip) можно написать на SQL так:
ОтветитьУдалитьupdate lc_cont set statuscode = :statuscode, … , statusmaster =(select mastercode from users… )»
-- Такой UPDATE посредством SQLObjects (представленная к рассмотрению техника) записать очень легко.
Но мы стараемся не злоупотреблять инструкцией SQL UPDATE в случае, если к этому нет прямых показаний, по ряду причин.
Самая простая состоит в том, что UPDATE нарушает инкапсуляцию таблицы, в которую вносятся изменения - используется знание правил, как там изменять данные. Например, корректировка записи в LC_CONT может представлять собой транзакцию, в которую могут быть включены другие таблицы.
Конечно, можно разместить соответствующую бизнес-логику на уровне триггера СУБД для модификации записей в LC_CONT, но это сразу же сделает эту бизнес-логику СУБД-зависимой, а выигрыш в производительности на атомарных операциях будет незаметным.
С другой стороны, использование DataSet.Edit и Post позволит разместить бизнес-логику в приложении (либо толстом клиенте, либо на сервере приложений) и сохранить независимость от СУБД, поскольку при выполнении Post будут активированы триггеры уровня приложения. Примером такого триггера является обработчик hnd_CON_TIME_ApplyTriggers, пример которого приводится.
Этот обработчик будет вызван при Post в наборе данных, открытом для таблицы CON_TIME (как следует из его имени).
не-не-не. вы не так поняли.
ОтветитьУдалитья имел ввиду не написание апдейта целиком, а замену строки
contact_data.FieldValues[nmStatusMaster] := CreateDataSetSelector
.UseDBC(DBC)
… .
на что-то такое, что бы сказало генратору SQL, мол в это поле подставь значение из другой таблицы без выборки его на клиент.
(а то что у вас эмуляция триггера - это понятно. ещё интересно было бы посмтореть на случаи, когда триггеров больше одного, как тригеры у вас регистрируются и т.п. просто ради любопытства)
«на что-то такое, что бы сказало генратору SQL, мол в это поле подставь значение из другой таблицы без выборки его на клиент.»
Удалить-- Почему-то мне кажется, что так не получится. Потому, что значение поля с именем nmStatusMaster должно быть определено *до* выполнения contact_data.Post (за это отвечает ForcePostData(contact_data)). Здесь принципиально, что операция изменения данных должна быть инициирована средствами DataSet.
«(а то что у вас эмуляция триггера - это понятно. ещё интересно было бы посмтореть на случаи, когда триггеров больше одного, как тригеры у вас регистрируются и т.п. просто ради любопытства)»
-- Пример другого триггера я привёл выше, регистрируются они так:
procedure RegisterTriggers(AConnection: TCustomConnection);
var
iObj: ITriggerContainer;
begin
if Supports(AConnection, ITriggerContainer, iObj) then
begin
iObj.RegisterTableTrigger(snLC_CONT, ProcToEventHandler(hnd_LC_CONT_ApplyTriggers));
iObj.RegisterTableTrigger(snCON_TIME, ProcToEventHandler(hnd_CON_TIME_ApplyTriggers));
end;
end;
а вообще, наши ораклисты внушили мне мысль, что триггеры сами по себе есть зло, и стоит их пименять либо для отладки, либо как временное решение (читай "костыль") при исправлении ошибок, но не как часть бизнес-логики.
ОтветитьУдалитьи я с ними начинаю соглашаться и смотреть на базы уже по другому.
«а вообще, наши ораклисты внушили мне мысль, что триггеры сами по себе есть зло, и стоит их пименять либо для отладки, либо как временное решение (читай "костыль") при исправлении ошибок, но не как часть бизнес-логики.»
Удалить-- Все "ораклисты" думают одинаково :-)
Есть замечательная статья "Где наша бизнес-логика, сынок?" http://habrahabr.ru/post/65432/ - возможно, она будет "в тему"...
спасибо за ссылку, почитал с интересом
УдалитьСильно смахивает на попытку реализовать сишарпный LINQ, только без поддержки анонимных типов со стороны компилятора/IDE.
ОтветитьУдалить