среда, 11 мая 2011 г.

Родная и не родная архитектура приложения

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

Все продукты разрабатывались продолжительное время, небольшими командами (до 15 человек) и в процессе выпуска очередных версии обрастали огромным числом "фичей".

Процесс проектирования был весьма условный и сводился к общим формулировкам типа "это все будет в виде сервиса и хранить все данные будем в SQL". Уже начиная с версии 2.0 продукт особой стройностью архитектуры не отличался.

Мне особо запомнился следующий случай - одна из больших частей системы писалась отдельной командой, влюбленной в Linux, и была написана и отлажена на С++\QT, но т.к. весь остальной продукт был сделан под Windows пользователям решили не мудрствуя лукаво поставлять виртуальную машину в которой работали окошки от этого компонента системы и взаимодействовали с основной частью по средствам CORBA . Поддерживать этот продукт было просто нереально. Даже просьба прислать логи превращалась в замечательный квест.

При разработке другого коробочного продукта под Windows .NET, разработчики решили, во чтобы то не стало воспользоваться языком Python, причем использовать именно его нативную версию т.к. IronPython на тот момент имел трудности при запуске на x64 машинах. Основными целями применения Python была именно скорость разработки, наличие исходного кода на стороне клиента и удобство написания тестов. И надо сказать что все эти цели были достигнуты, Python и правда очень приятный язык.

Дьявол как известно кроется в деталях. Оказалось что для того чтобы запускать этот код у клиента, нужно собрать MSI содержащий более 6000 файлов, включив туда уже установленный Python. А так как продукт предусматривал наличие веб интерфеиса, то пришлось тащить с собой и Apache и Django и еще огромную кучу всего. Так же в процессе разработки решались ряд специфичных трудностей с вызовом .NET API из Native Python через COM.

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

В результате с учетом того что к моменту начала внедрения этих технологии специалистов в них не было вообще и все члены команд в спешном порядке улучшали свои знания методом проб и ошибок, скорость разработки поднялась лишь весьма условно. Идея с тем чтобы при необходимости править код прямо на стороне заказчика за 3 года пригодилась считаное количество раз. Явным и неоспоримым плюсом стало то что всем было реально интересно осваивать что то новое.

Сейчас я снова вижу очередной коробочный продукт под Windows на начальной стадии где пытаются использовать Java, MySQL и Apache TomCat... Простая попытка поставить этот продукт и посмотреть на чистой виртуальной машине заняла у меня почти пол дня, для того, чтобы правильно поставить и сконфигурировать все части. При этом, как давний пользователь Windows, я был удивлен видеть в корне моего системного раздела папки "var" и "etc"

К чему бы я все это? А к тому, что какие бы не были новые и замечательные технологии, применять их стоит все же к месту. Если цель продукта массовый и конечный потребитель, то стоит задуматься об использовании именно тех технологии, которые уже есть в его системе.

Если пишется Enterprise level софт для Windows, то не грешно пользоваться .NET,IIS,ASP etc. Эти технологии тоже интересны и с ними также можно применять и Unit-тестирование,IoC,элементы функционального программирования и все другие техники.

В чужой монастырь со своим уставом не ходят!

вторник, 10 мая 2011 г.

Item ModerationInformation and SPListItem.UpdateInternal()

Столкнулся с необходимостью изменить значения нескольких полей включая Approval Status у ряда Item'ов, причем так, чтобы не изменились время модификации, Editor и не создалась дополнительная версия.

Первый способ был весьма прост:

var url = @"http://server/Lists/ContentApList";
var web = new SPSite(url).OpenWeb();
var list = web.GetList(url);
var item = list.GetItemById(1);
item["MyCheck"] = "test";
item[SPBuiltInFieldId._ModerationStatus] = 0;
item[SPBuiltInFieldId._ModerationComments] = "my coment";
item.SystemUpdate();
 
Но увы этот код просто валился с ошибкой Item does not exist. The page you selected contains an item that does not exist. It may have been deleted by another user.. Замена метода на UpdateOverwriteVersion() и Update() так же не помогла.

Вот такой вот вариант работает ,но :

var url = @"http://server/Lists/ContentApList";
var web = new SPSite(url).OpenWeb();
var list = web.GetList(url);
var item = list.GetItemById(1);
item["MyCheck"] = "test23";
item.ModerationInformation.Status = SPModerationStatusType.Pending;
item.ModerationInformation.Comment = "my coment";
item.SystemUpdate();
 
Но увы только для итемов. Похожий код для работы с файлами выдает исключение "You cannot change moderation status and set other item properties at that same time."

Попытка делать обновление в два этапа, увы так же не работает т.к. SystemUpdate, похоже, просто игнорирует обновление полей Moderation Information.

var url = @"http://server/Lists/ContentApList";
var web = new SPSite(url).OpenWeb();
var file = web.GetFile("CheckDocLib/logo.gif");
var item = file.ListItemAllFields;
item["MyComments"] = "test23";
item.SystemUpdate();

item.ModerationInformation.Status = SPModerationStatusType.Denied;
item.ModerationInformation.Comment = "my coment";
item.SystemUpdate();
 
Замена же на UpdateOverwriteVersion() приводит к тому что меняется время модификации.

Казалось бы приплыли, решения нет. Но пока еще бесплатный Reflector при просмотре ф-ии Update(), UpdateOverwriteVersion() и SystemUpdate() выдает что все они вызывают один и тот же метод:

internal void UpdateInternal(bool bSystem, bool bPreserveItemVersion, Guid newGuidOnAdd, bool bMigration, bool bPublish, bool bNoVersion, bool bCheckOut, bool bCheckin, bool suppressAfterEvents, string filename)

 

Табличка параметров вызова UpdateInternal из каждой ф-и.

Method bSystem bPreserveItemVersion newGuidOnAdd bMigration bPublish bNoVersion bCheckOut bCheckin suppressAfterEvents
Update() false false Empty false false false false false false
SystemUpdate() true false Empty false false false false false false
UpdateOverwriteVersion() false false Empty false false true false false false
MySystemUpdate() true false Empty true false false false false false

Немного поэсперементировав оказалось, что если выставлять флаги bSystem и bMigrate обновление ModerationInformation работает и при этом не происходит создания дополнительных версии. Вот итоговый код:

static void UpdateInternal(SPListItem item , bool bSystem, bool bPreserveItemVersion, Guid newGuidOnAdd, bool bMigration, bool bPublish, bool bNoVersion, bool bCheckOut, bool bCheckin, bool suppressAfterEvents)
{
    Type type = typeof(SPListItem);
    MethodInfo method = type.GetMethod("UpdateInternal",
                                        BindingFlags.Instance | BindingFlags.NonPublic,
                                        null,
                                        new Type[] { typeof(bool), typeof(bool), typeof(Guid), typeof(bool), typeof(bool), typeof(bool), typeof(bool), typeof(bool), typeof(bool) },
                                        null
        );

    var parameters = new object[] {
            bSystem,
            bPreserveItemVersion,
            newGuidOnAdd,
            bMigration,
            bPublish,
            bNoVersion,
            bCheckOut, bCheckin,
            suppressAfterEvents };

    object obj = method.Invoke(item, parameters);
}

static void UpdateMigrate(SPListItem item)
{
    UpdateInternal(item, true, false, Guid.Empty, true, false,false, false, false, false);
}

static void CheckList5()
{
    var url = @"http://server/Lists/ContentApList";
    var web = new SPSite(url).OpenWeb();
    var file = web.GetFile("CheckDocLib/logo.gif");
    var item = file.ListItemAllFields;
    item["MyComments"] = "test23ddd";
    item.ModerationInformation.Status = SPModerationStatusType.Approved;
    item.ModerationInformation.Comment = "my coment";
    UpdateMigrate(item);
}
 

Буду рад если кому пригодится.