12/10/2011 10:46:28 AM

У меня нет LiveJournal и он мне не нравится. Но вот Лена отчего-то странного к нему прикипела и пользуется.
А зато мне нравится Windows Live Writer для написания блог-постов. Удобненько всё, визуально. Я и сейчас в нём пишу. 
В отличие от того же LiveJournal, в котором Лена периодически руками вставляет какие-то теги, отдельно заливает фотографии и потом вставляет адреса, опять же руками… Она, мне кажется, больше с HTML работает, чем я и скоро знать его будет лучше…

LiveJournal имеет “стандартный” интерфейс, который имеют большинство блог-платформ и который поддерживается Live Writer’ом, но, к сожалению, феномен “was not invented here” добрался и до LJ и развитие поддержки стардартных протоколов они остановили, а вместо этого придумали свой собственный интерфейс, которого не понимает никто.
Таким образом Live Writer хоть и может работать с LJ, но на очень базовом уровне (сколько ребята осилили от общепринятого протокола): нет возможности управлять видимостью постингов, нет возможности добавить теги и т.д.

Поэтому мне пришла идея попробовать написать расширение для Live Writer, которое бы позволило работать с этой платформой более полно. Ниже я напишу что получилось, а ещё ниже – как это получилось.

Что.

Итак, после установки расширения в списке расширений появляется следующий пункт:

При нажатии на него пользователь увидит вот такое вот окошечко, во время показа которого система подключится к LJ и получит сведенния об имеющихся тегах и пользовательских группах:

Естественно, ничего подобного не произойдёт в том случае, если текущий выбраный блог в Live Writer не LJ. В этом случае вообще ничего не произойдёт :)

После этого в текущий пост будет вставлен невзрачного вида блок со специфичными для LJ параметрами:

Этот блок можно мышкой перетаскивать куда угодно, он информационный и не будет вставлен в постинг. Ещё раз: за пределами Live Writer этого блока не будет и никто его не увидит в LJ, включая автора. Наличие этого блока немного связано со спецификой механизма расширений Live Writer (о которой ниже) и с полным отсутствием каких-либо дизайнерских способностей у меня. Этот блок отображает настройки видимости постинга и ассоциированные с ним теги. Ну и, очевидно, в пост можно вставить только один такой блок.

При клике на этот информационный блок Live Writer (как и в случае других плагинов) сбоку покажет сайдбар, где эти самые специфичные для LJ параметры и редактируются.

В текущей бета-версии можно задавать теги для постинга а так же ограничивать видимость для определённых пользовательских групп. Ещё там можно задать текст для “ката” (тег <lj cut>).

Больше, в общем-то, пока в плагине ничего нет. А, нет, если посмотреть на первую картинку, то там ещё один пунктик есть: Add LJ User (добавить LJ-пользователя), но это мелочи.

Скачать версию плагина (beta1) можно здесь: http://storage.raga.name/Wlw4LJ-beta1.zip
Установить тоже просто: разархивировать и скопировать те два файла, что там есть, в каталог
"c:\Program Files (x86)\Windows Live\Writer\Plugins"
Если такого каталога с “x86” нет, то используется каталог без оного, то есть "c:\Program Files\Windows Live\Writer\Plugins".

Вроде работает, посты-картинки постит, драфты даже уважает…

Как.

Теперь самое весёлое, “для тех кто понимает” ;)
Так как у LJ свой API, то, очевидно, необходимо написать свой провайдер, умеющий работать с этим API. Провайдеры в WLW есть, наследуйся да пиши, а вот возможность подключить собственный провайдер туда добавить отчего-то забыли. Поэтому механизм добавления нового провайдера называется DirtyHack и выглядит (в коде) соотвествующе.

Аналогично в WLW отсутствует возможность (либо я таковой не нашёл) хоть какого-то хука на запуск приложения или на его инициализацию. И никакие хаки тут не помогут. Любой плагин получает управление только в момент его вызова.
Поэтому функциональность добавления LJ-специфичного провайдера (тот самый DirtyHack) срабатывает в момент первого вызова плагина (нажатия на Add LJ Attributes). Это в чём-то лимитирует, так как при наличии хука на стар приложения можно было бы использовать некие имеющиеся, но скрытые для “оригинального” LJ-провайдера элементы управления. А когда пользователь уже нажал – ну, тут уже поздно.
Кроме того в WLW отчего-то невозможно добавить кнопки управления на Ribbon. Этому я не поверил и проверил, потом не поверил снова и проверил опять, но со словами “ну как так можно-то!” отстал. Ну, либо, опять же, не нашёл. Поэтому – только меню Add LJ Attributes. А так бы, конечно, хотелось бы Ribbon-панельку добавить.

Про протокол, опять же.
Я поскольку ленив, то писать имплементацию XmlRpc-протокола для общения с LJ мне не хотелось. Я поискал в интернете и обнаружил некую библиотечку с названием lj_net, которая, вроде как, общается с LJ и имеет все нужные мне функции и которую люди в разных поделках типа моей и используют.
Но, видит небо, более ужасного кода я не встречал уже давно. Иногда это смешно, иногда – страшно. Ответа на вопрос почему ЭТО сделано ТАК не существует.
Пришлось рефакторить, конечно, стало куда лучше, хотя и не идеально.

Идеально было бы таки написать уже свой XmlRpc провайдер (благо WLW содержит низкоуровневый набор для работы с XmlRpc), надо только его использовать. Для пользователя, конечно, разницы никакой практически, но тем не менее…

Плюс ещё есть набор фич, которые можно реализовать, от moods и до странной и повергающей меня в ступор функциональности “этот пост могу видеть только я!”. Ведь, если его можешь видеть только ты, зачем ты его в социальную сеть-то постишь, человече?! Положи себе в записную книжку, в файлик, в EverNote какой-нибудь, но в блог-то зачем?!
Ан нет, фича, говорят, страсть как полезна и нужна :)

Планы.

Может быть буду обновлять, если будет хватать рук.

9/14/2011 8:01:00 AM

Виндовс 7 официально побила по количеству установленных копий своего главного конкурента - Виндовс ХР!
Более 450 миллионов копий Виндовс 7 уже установлено в мире. За время её существования было сделано 1502 обновления (не имеющих отношения к секьюрити, именно улучшения)!
542 миллиона активных пользователей (те, которые периодически логинятся в систему) насчитывает сейчас Windows Live!

А мы возвращаемся к топику :)

Одна из важных новостей - Виндовс 8 завтра можно будет скачать. Developer Preview Edition x86 & 64. Никакой активации, но и никакого саппорта :) Кроме разработчиков система вряд ли кому-то будет интересна, ибо это ещё даже не бета. Впереди все стандартные фазы: Beta, RC, RTM, GA. Сроков не называли (Майкрософт давненько бросила называть сроки), по слухам RTM ожидают к февралю следующего года.

Виндовс 8 работает быстрее 7-ки и требует меньше памяти. В два раза меньше, если верить скриншотам (200 мегабайт против 400 для 7-ки).

Над производительностью тоже много и упорно работали, в результате система стала гораздо быстрее. В качестве доказательства был продемонстрирован старенький (два или три года назад купленый) нетбук с гигабайтом ОЗУ, Виндовс 8 чувствовала там себя неплохо. Из того, что было сделано:
- Серьёзно работали над производительностью системы в целом.
- Переписали XAML Engine полностью на нативный C++, что, собственно, позволяет приложениям на С++ его использовать, а вызовы объектов, описываемых XAML теперь просто компилируются как вызовы методов виртуальной таблицы, что, конечно, очень быстро.
- Ещё несколько пунктов, важных, важнее предыдущего, хотел написать, но вылетели дважды из головы! Вспомню - напишу.

Оставлена полная совместимость с Виндовс 7, как было заявлено - абсолютно полная, никаких здесь компромисов.

Озвучена и продемонстрирована поддержка ARM-архитектуры, правда, там ещё далеко не всё гладко, поэтому и скачать этот билд пока нельзя. Но прогресс очень большой, так как на прошлой демонстрации они ничего кроме десктопа на ARM продемонстрировать не могли.

Приложения для Виндовс 8 как-бы поделили на две части: "обычные", и "метро-стайл". Про "обычные" никто ничего не говорит - они как были - так и остались, ничего (пока что) нового, только всё быстрее.
С метростайл приложениями всё интереснее. Виндовс 8 включает в себя целый новый стек для такого рода приложений. Снизу этого стека лежат сервисы ядра (которые так же используются и Вин32 инфраструктурой, и дотнетом, и сильверлайтом и т.д, но которые не включены в стек метро-стайл приложений). Поверх сервисов ядра лежит то, что называется Windows Runtime или, сокращённо, WinRT APIs. Это как раз та штука, о которой ходили слухи, что она будет являться заменой Win32 API. Насколько пока можно судить, так оно и есть.
Поверх WinRT располагаются языки программирования, которые могут работать с этими АПИ. Это могут быть в равной мере С++, C# или Visual Basic (и тогда в качестве разметки интерфейса используется XAML), либо же JavaScript (и тогда интерфейс делается на HTML5).

Вот на этом слайде видно, примерно, как это выглядит. Обратите внимание, что под WinRT APIs нет Win32.
http://community.devexpress.com/blogs/ctodx/Windows8Architecture_485458D4.png

Важно ещё раз отметить, что и XAML, и сам WinRT - компoненты "нативные". Если приложение делается на С++, то тут и вопросов никаких не возникает ни с производительностью, ни с.. как по-русски "интероперабилити"? :)
Под сишарпом и вижлбейсиком, однако, никакого дотнета нет. Он есть, но в другом месте, вне пределов метро-стайл приложений. Означает ли это, что сишарп и бейсик компилируются в нативный код? Я не знаю. С одной стороны, моно умеет это делать, почему бы и майкрософту не суметь? С другой стороны - мне в это не верится, особенно глядя на стоящий рядом кубик с javascript, который, вестимо, вообще никуда не компилируется. Очень надеюсь узнать подробности в ближайшие дни.

Не смотря на то, что WinRT "нативный", вызывать его из C# и даже Javascript не составляет никаких проблем. Обычное инстанцирование и использование нужного объекта, например, Camera.
Каждый API WinRT кроме библиотеки нативного кода содержит ещё и файл с метаданными. Физически это файл того же формата, в котором хранит метаданные CLR. Эта инфраструктура называется WinMD, а файлы, соответственно, MD-файлами. Они как-то используются для рефлексии как раз для того, чтобы WinRT могла быть использована отовсюду.
Как это конкретно работает у меня есть догадки, но пока писать не буду, подожду более точной информации.

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

В конце дня каждому выдали по две коробки (кстати, быстро разобрались с пятью тысячами участников, минут 10 всего в очереди стоять пришлось). В одной из коробок оказался планшетный компьютер Самсунг, в другой - синезубая клавиатура к нему и перо.
Диагональ планшета 11.6", экран - какой-то крутой самсунговский, который яркий и с углами обзора до 180 градусов. Экран не из пуленепробиваемого стекла, к сожалению, так что, наверное, нужна плёнка.
Процессор - Core i5 (ARM-то пока не готов), SSD на 64 гигабайта, симка с годовым обслуживанием по 2 гигабайта трафика в месяц (к сожалению, только на территории США). Имеется кредл и зарядник для планшета.

Планшет поддерживает до двух мониторов, на нём стоит предварительная версия Visual Studio 2011 и можно на нём же писать приложения для него же. Отлаживать код можно как прямо на планшете, так и в эмуляторе.
С эмулятором интересно, он работает через RDP к самому же планшету :)
С отладкой тоже интересно сделали в 2011-й студии: можно отлаживать приложение удалённо по сети, включая сеть беспроводную. То есть, с десктопной машины можно скомпилировать и запустить приложение на лежащем рядом планшете (по беспроводной сети), при этом работает всё, от интеллитрейса и всяких там отладочных прибамбасов и до DOM-инспектора с поиском элемента, на который укажут.

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

7/10/2011 1:04:20 PM

Шаблон “Saga” используется для моделирования long-running (Как это будет по-русски? Долгосрочные? Долгоиграющие?) бизнес-процессов. Фактически, мы можем сказать, что Saga представляет собой Workflow для какого-то определённого сценария.

Long-running не следует понимать в терминах секундной стрелки и задаваться вопросом: 200 милисекунд – это long-running или нет? В системах с архитектурой, построенной на событиях и сообщениях подобные вопросы вряд ли имеют смысл.

Для примера рассмотрим ситуацию, когда дл�� исполнения какой-то операции требуется совершить вызов каких-то двух веб-сервисов.
Само по себе время здесь даже не очень-то и важно, важно здесь то, что мы больше не можем гарантировать transaction consistency нашей операции.
Что произойдёт в том случае, если в промежутке между этими двумя вызовами произойдёт исключение? Что произойдёт, если вызов второго веб-сервиса завершится неудачей? Что произойдёт в случае, если оба веб-сервиса сработали удачно, но мы не смогли подтвердить транзакцию в нашей собственной базе данных по каким-то причинам? (в этом случае нам даже двух веб-сервисов не нужно, достаточно одного).
Мы можем отменить нашу транзакцию (а в случае исключения это произойдёт автоматически), но мы не можем отменить наш вызов к первому веб-сервису, так, как он не является частью этой транзакции. По тем же причинам мы не можем просто повторить команду, надеясь, что уж в этот-то раз всё точно сработает успешно, так как это приведёт к повтору вызова веб-сервисов.

Вот это и есть long-running процесс. Это и есть те самые бизнес-правила (точнее, их реализация, модель), о которых я писал в постинге про CQRS.

Long-running процессы подразумевают состояние (state)

Идея, которую реализует шаблон “Saga” проста: после каждого успешно выполненного шага мы имеем некоторое состояние, с которого можно будет продолжить исполнение процесса. Шагом является выполнение какого-то дейтсвия, реакция на какое-то событие и т.д.
То есть, если мы не смогли подтвердить транзакцию в базу данных, или если вызов второго веб-сервиса завершился неудачей, у нас имеется состояние, валидное на момент до его вызова.
Наш бизнес-процесс остановлен – это да, но он и не потерян. Мы можем предпринять какие-то действия и продолжить процесс. Как результат – процесс просто выполнялся дольше.

Кроме того, имея такое состояние, мы можем легко моделировать процессы, “управляемые” событиями!

Старый пример: Если произошли оба OrderAccepted и OrderBilled, то можно приступать к отправке товара клиенту. События OrderAccepted и OrderBilled в системе могут произойти в любом порядке, с любым промежутком времени между ними.
Например, OrderAccepted начинает бизнес-процесс (Saga) для этого заказа и мы имеем некое состояние, в котором отмечаем, что это событие произошло. Когда происходит OrderBilled, Saga отмечает и это в своём состоянии.
Когда получены оба события, можно приступать к отправке заказа.

В реальности такая Saga может быть чуть более сложной:

  • Создать Saga при получении любого из событий OrderAccepted и OrderBilled.
  • При получении второго события приступить к отправке товара.
  • При получении OrderCancelled немедленно прекратить свою работу.
  • Если получено одно сообщение, но не получено второе в течение заданного времени, дать знать о том, что с заказом что-то не так в соответствующий департамент.
  • <какие-то ещё бизнес-требования>

Бизнес-процесс моделируется как Saga

Очень удобно моделировать бизнесс-процесс через шаблон Saga как минимум потому, что процесс в этом случае получается автономным. Saga реагирует на события и выполняет какие-то действия, этот процесс самодостаточен, не связан (ничем, кроме событий) с другими процессами системы.
Saga инкапсулирует своё состояние внутри себя, самостоятельно хранит в себе нужную в рамках процесса информацию о каких-то фактах (заказ оплачен, заказ подтверждён) и эта информация больше никого не касается.
Saga описывается в одном месте и легко поддерживается и обновляется (в противовес, возможно, стандартному решению: добавим к Order колонки Billed и Shipped, для вычисления промежутков времени напишем класс, который будет вызываться периодически каждые 10 секунд, проверять базу данных, как-то дёргать нужный метод и т.д).

Важный аспект: при создании Saga всегда нужно задаваться вопросом времени. Что должно случиться, если время ожидания вышло? Как долго мы можем позволить себе ждать?
Не потому, что тут есть какие-то технические проблемы в том, что Saga будет “жить” долго, а потому, что мы моделируем бизнес-процесс. А в бизнесе ничто не может длиться вечно и задержки часто означают проблемы, новые бизнес-правила и т.д.

Реализация шаблона Saga
Здесь код будет часто более описателен, чем слова, поэтому я буду приводить пример на псевдокоде.
Это не код какого-либо фреймворка, а просто описание для понятности.

Saga можно описать как:

public sealed class MySaga : Saga<MySagaState>,
        IStartedBy<Message1>,
        IMessageHandler<Message2>
{
        public override void Configure()
        {
             ConfigureForMessage<Message1>(m => m.OrderId, s => s.OrderId);
             ConfigureForMessage<Message2>(m => m.ClientOrderId, s => s.OrderId);
        }

        public void Handle(Message1 message)
        {
             this.State.SomeData = message.SomeData;
             StartTimer(Timespan.FromMinutes(25));
        }

        public void Handle(Message2 message) { StopSaga(); }

        public void TimeOut() { StopSaga(); }
}

В этом примере у нас есть Saga, которая начинается с сообщения Message1 и реагирует на сообщение Message2. Понятно, что Saga может наследовать несколько IStartedBy<T>, что будет означать, что Saga начинается с любого из этих сообщений и будет потом реагировать на оставшиеся.
Сообщение Message2 не будет начинать новую Saga, однако, если Saga уже существует, сообщение Message2 будет ею получено. Естественно, Saga может наследовать IMessageHandler<T> несколько раз.

Saga в данном случае имеет состояние типа MySagaState.

Так, как мы можем (и будем) иметь в системе не один экземпряр Saga, а множество, необходимо определить, как сообщения будут коррелировать с экземплярами Saga (а точнее, с состоянием), для чего в приведённом выше примере используется метод Configure().
С помощью него мы указываем как найти подходящее состояние для полученного сообщения: Для Message1 корреляция делается между свойства OrderId в состоянии и в самом сообщении, для Message2 должны совпасть значения свойства ClientOrderId в сообщении и свойства OrderId в состоянии.

В случае, когда получено сообщение Message1 и нет экземпляра MySagaState с соответствующей корреляцией, такой экземпляр будет создан и Saga начнёт свою работу.
В случае, когда получено сообщение Message2 и нет экземпляра MySagaState с соответствующей корреляцией, сообщение будет проигнорировано и Saga не будет начата.

Таким образом мы можем иметь только один экземпляр MySaga для каждого значения OrderId.

Методы Handle(…) для каждого сообщения определяют, что нужно сделать, когда то или иное сообщение получено.

В приведённом случае при получении Message1 мы изменяем состояние и устанавливаем таймаут в 20 минут.
При получении сообщения Message2 мы просто останавливаем Saga, то же самое делаем по таймауту. Понятно, что можно делать что-то поумнее :)

Особенности реализации Saga

Saga является неким “оркестрирующим” элементом, описательным, так сказать (В MassTransit и RhinoBus даже интерфейс называется не IMessageHandler<T>, a Orchestrate<T>). Поэтому крайне рекомендуется не производить никакой “полезной работы” в Saga. Никаких запросов к веб-сервисам, никаких запросов к базам данных сервисов системы (разумеется).
Всё, что предполагается делать в Saga – это работа над состоянием на основании событий и сообщений. Полностью messaging-ориентированная архитектура.
Всё, что может делать Saga – это дождаться какого-то сообщения и послать какое-то другое сообщение.
Если Saga что-то “хочет” для своей работы, то этого можно добиться послав команду и дождавшись ответа.
Представьте себе директора в своём кабинете с листочком бумаги и карандашом. Ему поступает инфорамация о том, что происходит в различных подразделениях компании, он что-то чёркает на листочке и периодически отдаёт указания. Когда ему нужна какая-то информация, он просит ему её доставить.
Это и есть Saga :) В промежутках между этими сообщениями менеджер-Saga занимается полезной работой – спит.

Такое требование обосновано с точки зрения декомпозиции: каждый занимается чем-то одним, Single Responsibility Principle. Saga – оркестрирует бизнес-процесс, руководит.
С точки зрения производительности это тоже обосновано: при получении сообщения Saga находит/создаёт коррелирующее состояние, вызывает соответствующий метод Handle(…) для соответствующего сообщения, после чего сохраняет состояние и завершает свою работу.
Чем быстрее Saga обработает сообщение, тем больше сообщений может быть обработано в единицу времени.

Поэтому Saga – это не очередной класс на 574 строки, который делает всё, а маленький message-ориентированный кусочек бизне��-логики :)

Аналогия
Можно провести какую-то аналогию с Windows Workflow Foundation, но в случае последнего это гораздо менее гибко с точки зрения поддержки, требует больших усилий по написанию (Saga всё же простой класс), менее гибко с точки зрения сохранения состояния (WF сохраняет состояние instance’а когда ей кажется, что instance ничего не делает) и горадо менее удобно в messaging-ориентированной среде и имеет большие проблемы с версионностью.

Версионность и Saga
Здесь всё достаточно тривиально. 
Так или иначе мы имеем два варианта изменений: “Новый вариант Saga должен заработать немедленно, мы пофиксили баг” и “Старые Saga должны закончится как есть, а вот заново должны начинаться новые варианты”.

В случае первого всё понятно: мы должны гарантировать обратную совместимость состояния и проблем больше нет.

В случае второго может быть использована такая стратегия:

  • Создаём специальный IMessageHandler, который вызовется перед Saga и проверит, существует ли уже экземпляр состояния для этой Saga.
  • Если экземпляр существует – ничего не делаем, текущий экземпляр Saga выполнит свою работу.
  • Если такой экземпляр не существует, то прекращаем обработку сообщения, а само сообщение посылаем в очередь “новой” версии Saga. То есть, фактически мы роутим сообщение в новую очередь V2.
  • Когда “старых” экземпляров не осталось, мы можем просто перевести “новую” версию Saga на ту очередь, которую слушает “старая”, а “старую” просто убрать из системы вместе с тем IMessageHandler, который осуществлял роутинг.
    Система приходит к первоначальному виду.

sagas

Промежуточный результат

Sagas как бизнес-процессы, обрабатывающие бизнес-правила и есть та часть CQRS, которая отвечает за всю внутреннюю “магию”. Состояния Sagas и есть то, что хранится в базе Command Database, в которой до появления Persisted ViewModel хранились сами данные.
Эта база размещается настолько быстро к серверам, занимающимся непосредственной бизнес-логикой, насколько можно. Эта база достаточно мала, так как хранит информацию только о бизнес-процессах, которые находятся в процессе своей работы на том или ином этапе. После того, как процесс завершён, эта информация больше не нужна и может быть удалена из БД.

А, да. Напоминание. Всё это время мы находились в пределах одного BC ;)

Вот, собственно, и всё, что я хотел написать про CQRS как таковой.

7/8/2011 3:38:24 PM

Ещё немного про “простые” AC/BC, прежде, чем заняться CQRS.
Так, как “простых” AC в любой системе будет большинство и для них CQRS не нужен, отложим его пока на попозже :)

Persisted ViewModel
В прошлый раз я описывал идею того, как AC сохраняет и читает данные из схемы (БД), принадлежащей BC.

В этой связи стоит обратить внимание на то, что AC такого рода являются некими data-centric, или даже data-driven элементами. Показал данные в UI, пользователь поменял что-то в текстбоксе, сохранил в БД.
Понятно, что операция чтения выполняется гораздо чаще, чем записи.

А раз она выполняется чаще, то можно сделать её быстрее! Для этого можно организовать ещё одно хранилище, базу данных, настолько близко к веб-серверу, насколько можно. Можно сохранять/кешировать нашу инфомарцию там так, чтобы доступ к ней был быстрее.
Можно посмотреть на это с такой стороны, что каждая такая операция AC возвращает определённую, по сути, ViewModel, которая в основном содержит достаточно мало данных (не забываем, что мы находимся в пределах BC и имеем дело только с данными, которые нужны только этому BC, у нас больше нет гигантских объектов).
Тогда можно попытаться сделать так, чтобы одна ViewModel занимала, условно говоря, одну строку в этом новом хранилище-базе-кеше, так будет быстрее эту ViewModel оттуда доставать.

Причём, если мы сумели сделать ViewModel как одну строку, или сущность, то нам, по сути, уже нет разницы, какое хранилище использовать: будет ли это SQL Server (ViewModel per row), будет ли это какая-нибудь Mongo или CoachDB или другое NoSQL-решение (Document per row), главное, чтобы было удобно и быстро, можно даже в файлах хранить :)

Классифицируем это новое хранилище как “Persisted ViewModel”. То есть, в рамках BC мы имеем теперь ещё и Persisted ViewModel, который будет разворачиваться настолько близко к клиентам, насколько возможно, чтобы увеличить скорость работы с ним.

Валидация
Пойдём ещё дальше, подумаем о валидации.

Мы, программисты, иногда путаем валидацию и бизнес-правила. Udi отметил, что нужно чётко разделять эти две категории.
Например “максимальная длина имени пользователя – 25 символов”. Это – валидация.
”Email должен быть уникальным” – это валидация.
Мы часто делаем валидацию на стороне клиента, потом ещё раз проверяем на стороне бизнес-логики, возвращая ошибки, чтобы показать их красненьким и всё такое.
Зачем – большой вопрос. Почему мы так не доверяем собственным клиентам, что валидируем (речь не идёт о бизнес-правилах) их ввод дважды, удивлялся Udi. Что случится, если мы не будем делать валидацию на стороне бизнес-логики?
Наша команда не выполнится, возникнет исключение, “имя пользователя слишком длинное и не влезло в SQL-сервер”, что-то в этом роде.
Почему это может случиться? Например потому, что кто-то попытался обойти валидацию на клиентской части. Какой-нибудь хакер.
Почему мы так заботимся о том, чтобы хакер получил красивые красненькие сообщения, связанные с валидацией, откуда такая забота о хакере – большой вопрос. “Знаете что, я считаю, что хакер даже заслуживает того, чтобы получить ошибку “ваша команда не была выполнена”! Особенно если она будет доставлен ему асинхронно по email!” заявил Udi. :)

Тем не менее.
Persisted ViewModel может использоваться так же и для валидации пользовательского ввода. То есть, нам нужно проверить уникальность пользовательского email, мы можем сделать это с использованием расположенного близко к веб-сервису Persisted ViewModel хранилища.

Промежуточный итог
У нас получилась ситуация, когда простые AC, работающие с неконкурентными данными, имеют возможность быстро-быстро работать с Persisted ViewModel. Причём, поскольку данные неконкурентны, мы можем сразу же в неё и писать прямо внутри этого самого AC, когда пользователь добавляет/изменяет данные.
Сделать это крайне несложно: простые AC, партицированные по business capability и принадлежащие только одному BC данные, ничего лишнего, остаётся только привести их к виду 1 запись – 1 ViewModel в идеале. А часто и это не потребуется, так как такая ситуация часто будет иметь место уже как результат этого вертикального партицирования.
Ещё более того. В таком data-driven AC часто не нужен никакой “слой бизнес-логики”, значит можно пойти ещё дальше и “разговаривать” напрямую с Persisted ViewModel! Особенно это просто, когда в этом качестве выступает NoSQL база данных, они практически все имеют REST/JSON интерфейсы, можно взять данные прямо оттуда и положить данные прямо туда. Нам не нужен никакой DAL, ORM, Database Context, что-то ещё.
Даже SQL Server может выставить наружу по HTTP (если нет возможности “нормального” соединения с сервером) хранимую процедуру. Впрочем, в простоте и удобстве этого решения я как-то не слишком уверен, но вариант такой есть, зато DAL делать не надо.

Один из случаев, вызвавших вопросы в нашей группе (и вызывающих вопросы всегда, как сказал Udi), это то, что, разумеется, в одном BC находятся, например, Order и OrderLines.
Однако, если посмотреть на то, что представляют из себя обе эти сущности, то можно заметить, что Order в рамках BC – это всего лишь OrderId. Ну, CustomerId ещё. А OrderLines – это просто список ProductIds. Больше ничего там нет.
Так ли важно в данном случае хранить две таблицы и делать связь между ними, если у нас там только одни идентификаторы?
Можно, например, сделать из списка ProductIds строку, разделённую запятыми. Not a big deal!

Вместо гигантского объекта Order получаем два Guid’а и ещё список Guisd’ов, разделённых запятыми, одна строка.
”So, where is THE Order?” – это шутка, над которой наша “подгруппа” смеялась на перерывах и вопрос, который в шутку задавался друг другу как фраза для начала какого-то разговора. Ну, например, подходит человек к группе разговаривающих и присоединяется фразой “so, guys, where is the order?”
Потому, что никакого объекта “Order” на самом деле в системе нет.

Вот в следующий раз – точно про CQRS. Обмозговать бы ещё малость.

7/7/2011 3:44:59 PM

From AC
Within BC
You may do some RPC

//SOA Rap

Делить на части в SOA – это как раздевать капусту (Это я продолжаю заметки по ходу курса. Лучший способ утрясти в голове – попробовать рассказать кому-то).
На сервисы поделили – можно делить дальше! :)

Business Components

Сервис, олицетворяя собой business capability, состоит из одного или более Business Component (BC).
Чаще всего всё начинается с одного BC в рамках сервиса и, чаще всего, так и заканчивается.

Однако, иногда возникают ситуации, когда код сервиса начинает “ветвиться”. Udi привёл такой пример: допустим, в наш магазин обратился Стив Балмер, который хочет купить 10 000 единиц товара. Станем ли мы обслуживать его как обычного кастомера? Скажем ли мы ему: “добрый вечер, Стив, спасибо, что зашли, встаньте в конец очереди, пожалуйста, не желаете ли стакан воды”? Попросим ли его в качестве подтверждения дать нам личную кредитную карту на случай? (учитывая, что у Майкрософт есть всего две схемы оплаты и по какой они намерены расплачиваться, они выбирают сами)
Скорее всего нет. А тут ещё Стив Джобс обещал на неделе заглянуть…

В общем, в коде нашего сервиса начинают появляться какие-нибудь условия вроде if (strategicCustomer) {…}.
Раз появилось, два появилось, три появилось – и вот тут уже надо задуматься, а не имеем ли мы дело с другим бизнес-кейсом (в рамках того же capability)?
Рекомендуется в этом случае поговорить с людьми со стороны бизнеса, так как можно получить как ответ “нет, все кастомеры у нас одинаковы, это вот единственное такое исключение”, так и ответ “да, у нас есть определённая категория привелигированных кастомеров, с которыми мы работаем по несколько иной схеме”.
Часто, продолжив разговор, можно обнаружить, что на самом деле бизнес потенциально хотел бы работать с такими кастомерами по ещё более иной схеме, но даже не задумывался над этим, так как давно привыкли думать в рамках того, что позволяет наш софт. И, конечно, на будущее ещё есть планы (Стив Джобс ведь и друзей приведёт).

Если это различие существенно – ура, у нас появился отдельный кейс, выделяемый в отдельный BC.

Создаётся новый BC так: берём тот, что у нас есть, делаем полную копию, убираем из первого всё, что касается кастомера стратегического, из второго – всё то, что касается кастомера обычного.

Отдельно подчёркивается: кроме BC сервис не содержит ничего, кроме, может быть, неймспейса.
Правила простые:

  • Никакого кода помимо кода, содержащегося в BC, в сервисе нет вообще.
  • Два BC друг с другом на уровне кода не связаны никак (никакого наследования одного от другого, никакого общего переиспользуемого кода)
  • Сба BC “выглядят” идентично: они реагируют на одни и те же события, посылают одни и те же сообщения в тех же фазах.
  • Снаружи сервиса BC не видны, сколько их там и что там – не имеет значения, снаружи сервис – это по-прежнему просто сервис.

Таким образом, BC – это этакий мини-сервис, или реализация сервиса в контексте какой-то бизнес-вариации.

Было много вопросов относительно дублирования кода, на которые Udi отвечал примерно так: “если мы обнаружили, что бизнес-аспекты разные, то со временем они будут расходиться дальше, дальше и дальше”, что, в общем-то, имеет смысл.
Как в том примере со стратегическим клиентом: Быть может такому клиенту не имеет большого смысла показывать шоппинг-карту, как обычному клиенту? 10 000 наименований в карте вряд ли кому-то будут интересны. Быть может, схема оплаты для таких клиентов совершенно другая, с ними удобнее работать по предварительному инвойсу, а не по кредитной карте? Быть может схема базы данных для такого случая будет несколько отличаться от “обычной”? А быть может и вообще будет другая база данных?

До тех пор, пока все BC сервиса имеют одинаковый “интерфейс”, то есть, откликаются на одни и те же события (OrderApproved) и посылают одни и те же события (ClientBilled) они “взаимозаменяемы” и снаружи невозможно сказать, какой именно BC обработал событие.
А как они реализованы – их БиСи’шье дело. Ибо инкапсуляция и single responsibility principle.

Autonomous Components

В терминах кода BC представляет собой solution (как я говорил раньше о сервисе, когда сервис у нас был одним BC по сути), состоящий из разных “кусочков”: обработчики событий и сообщений, возможно проект схемы БД, возможно UI какой-то…

Всё это дело можно подразделить на набор неких Autonomous Components (AC).
Правила такие:

  • AC отвечает за определённые типы сообщений (в идеале – за один тип сообщения)
  • AC использует Bus (вход-выход)
  • Допустимы RPC calls в рамках одного BC (внутри AC – who cares, между AC – acceptable, за пределы BC – запрещено)

Важно ещё то, что AC – это единица развёртывания (deployment unit). То есть, AC могут быть установлены на на один или более серверов, в одном или более экземпляре и т.д.
Таким образом, да, BC (а, следовательно, и сервис) у нас может находиться физически на разных машинах и всё такое.

Я вот так себе это в OneNote зарисовал (и да, это у меня почерк такой, особенно когда вертикально на экране пером пишу):

AC, BC

Промежуточный результат

Так, как AC у нас это, получается, такие маленькие проектики, суть маленькие DLLs (единицы развёртывания), отвечающие за малое количество сообщений, то и кода там в них, по идее, достаточно не много.
То есть, мы получили такую декомпозицию, при которой чётко и понятно, что и в каком кусочке находится, при этом эти кусочки достаточно изолированы друг от друга и относительно просты (хоть и являются частью большой сложной системы).

Рассмотрим простой случай, когда производится работа над “неразделяемыми” данными, то есть, данными, которые не изменяются одновременно сразу несколькими пользователями. Например, user profile, или shopping cart (очень маловероятно, что в Вашу карту кто-то что-то ещё положит), или запрос в службу поддержки… Думаю, понятно, что практически в любой системе подавляющее большинство сценариев будет именно таким: крааайне маловероятно, что кто-то ещё будет менять те же данные (такие кейсы есть, но о них позже)

Поскольку AC относительно мал, отвечает за небольшой кусочек функциональности, конкурентности с данными никакой нет, то мы можем фактически свести его виду:

“Я умею сделать SELECT”. То есть, в достаточно большом количестве случаев нам может не понадобиться никакой дополнительной бизнес-логики в AC.
Показать цену для продукта?
Пожалуйста, SELECT * FROM Pricing WHERE ProductId = @id. Быть может, AND Date=@date.
По идее, поскольку данные у нас партицированы по сервису/BC, и мы уже не имеем имеем дела с некой таблицей Products, содержащей кучу полей, мы можем себе позволить эту звёздочку.
Мы можем себе позволить не иметь никакого ORM для этого AC и даже использовать DataSet. DataSet’ы – хорошие, пока не начинают применяться в немереных масштабах.
Это элементарно простой AC, выдающий цену продукта, профиль пользователя, наименование и описание продукта и т.д.

“Я умею обновить информацию”. Простой Update в б��зу данных. Мы не ожидаем, что кто-то ещё будет обновлять пользовательский профиль в то же самое время, это краааайне маловероятно, если не невероятно вообще. В “случае чего” же сработает правило “последний победил”, мы же всё равно так делаем! :) Что произойдёт в 99.9% систем, если на изменение профиля пользователя вдруг придут два запроса одновременно? (пользователь два раза нажал кнопку send?) Да ничего. Профиль запишется два раза и всего делов. Мы всё равно так делаем. По умолчанию. Но можно и не придерживаться умолчаний.

То есть, в достаточно большом количестве случаев наши AC будут простыми SELECT/UPDATE, что не может не радовать.

Для других, более сложных случаев с конкурентными данными используется CQRS, о чём, может быть, позже.

7/6/2011 4:39:32 PM

Ниже мои заметки с курса, кусками (середина уже):

SOA предполагает “разделение” системы на “сервисы”.

Сервис определён как “the technical authority for a specific business capability”.
Это значит, что предписывается строить приложение не с использованием bottom-up подхода, а с использованием top-down подхода.

Bottom-up подход:
Делаем систему для продажи товаров.

Значит у нас есть объекты:
Customer of LoginName * FirstName * LastName * Status * 
Address of AddressType * Country * City * Street
Product of Title * Description * Price
и т.д, разные другие логичные свойства и сущности

Делаем таблички для этих сущностей, это у нас слой БД. Берём EF4, это у нас модель. Делаем репозитории с операциями CRUD и всякими другими интересными, это у нас слой бизнес-логики. Делаем UI для всего этого дела.

Top-down подход:
Делаем систему для продажи товаров, но перестаём впихивать бизнес в термины базы данных.

Определяем business capabilities, то есть, изучаем бизнес и смотим, что и где происходит, какие данные изменяются совместно, какие требования изменяются совместно.
Приходим к мысли, что наш объект Customer из примера выше – сущность странная и не имеющая смысла ни в реальном мире, ни в мире бизнес-логики приложения. Остаётся загадкой, почему мы считаем её логичной.
Например, у меня, как у кастомера, есть имя и фамилия. Но у меня нет статуса. Этот “статус” существует для меня где-то отдельно от меня, причём в разных местах у меня могут быть разные статусы и т.д.

С точки же зрения бизнес-логики такая компоновка объекта Customer смысла тоже, скорее всего, не имеет.
Потому, что очень маловероятно, что бизнес-логике, осуществляющей работу c кастомером, когда-нибудь потребуется его логин или пароль. Очень маловероятно, что потребуется некий “статус” который, в свою очередь, будет требоваться там, где будет осуществляться работа с ценами и продажами, но там вряд ли кого-то будут интересовать не только логин и пароль кастомера, но и его имя, и его адреса.
Соответственно, с точки зрения биллинга кастомера, нас не интересует ничего, кроме кредитной карты и, может быть, биллинг адреса. Нас не интересует адрес доставки в этом случае так же, как бизнес-логике, отвечающей за доставку, не нужна информация о кредитной карте и адресе для биллинга.
Или, вот, скажем, продукт и его цена. Там, где мы имеем дело с описанием продукта, его спецификациями и т.д, его цена вряд ли имеет смысл. Более того, у продукта нет как таковой цены, ибо цена обычно есть функция времени, если бизнес-требования не диктуют фиксированых цен навсегда, чего они обычно не делают. Таким образом, цена продукта это всегда цена когда?.

Ну и так далее.
Получается, что определив определённые границы business capabilities для приложения мы получаем определённый набор сервисов.
В каждом из этих сервисов понятия Customer, Product и т.д. отличаются. Скажем, в сервисе аутентификации пользователь – это его логин и пароль, в сервисе продаж – статус, в сервисе биллинга – кредитная карта и адрес биллинга и т.д. и т.п.

Чётко определив границы business capabilities можно двигаться дальше и сказать, что каждый сервис технически – это solution в вижл студии, это отдельная ветка кода. Он никак не связан с другими сервисами. У него есть своя бизнес-логика, своя схема БД и всё, остальное, что полагается, если это нужно.

Такие сервисы легко изменять, ибо изменение в одном сервисе никак не затрагивает остальные части системы. Сервис полностью владеет своими схемами данных в частности.

Между собой сервисы могут обмениваться событиями, RPC между сервисами недопустимы.
Причём события одного сервиса имеют смысл в контексте самого сервиса и не более. То есть, сервис, занимающийся биллингом клиента, не может послать событие “а теперь заказ нужно отправить почтой”. Он только может послать событие “Клиент был забиллен”.
Сервис, занимающийся доставкой, подписывается на события, и это он и только он решает, когда можно оправлять товар, а когда ещё нет. Например, может быть нужно дождаться какого-то другого события кроме “клиент забиллен”, скажем, “заказ подтверждён”, и отправлять только тогда, когда оба эти события для заказа имели место произойти.

Тут вырисовываются интересные вещи.
Например, раз у сервисов разные базы данных, то мы не можем гарантировать referential integrity.
Но вопрос стоит в том: а нужно ли нам это? Если, скажем, мы получили событие “кастомер 5 положил в карту товар такой-то”, а где-то в другой базе пока ещё нет никакой информации о кастомере 5 (проблема с сетью, сервис “лежал”, обработка длится дольше и т.д), то является ли это проблемой?
С точки зрения IT-мозга – да.
С точки зрения бизнеса… Скорее всего – нет. Люди, наблюдающие за статистикой продаж, сильно вряд ли интересуются конкретными именами покупателей. Поэтому мы, скорее всего, вполне можем позволить себе получить событие о том, что товар положен в карту до того, как мы получили имя клиента.
Потому, что это разные business capabilities.

Поэтому мы можем иметь независимые друг от друга сервисы, которые могут быть установлены на одну и ту же, либо на разные физические машины, которые могут иметь совершенно разные структуры баз данных (или, что там говорить, вообще совершенно разные СУБД, где-то, быть может, будет более уместно использовать NoSQL и хранить там документы, где-то файловую систему и хранить там тонны сгенерированных по документам тайлов и т.д.)
Мы получаем возможность вертикального партицирования, а с ним – много других интересных возможностей, например, возможность перераспределить ресурсы и сделать какой-то аспект системы более приоритетным, нежели другой в терминах используемых ресурсов (например, размещение заказа гораздо более приоритетная операция, чем, скажем, отображение terms and conditions).

7/4/2011 4:01:43 PM

Курс по SOA с Udi Dahan понравился с первого дня, хотя, собственно, до самого SOA ещё совершенно не добрались.

Первый день – back to basics. Половину дня говорили, в частности, о проблемах, стоящих перед современными распределёнными системами.

Udi привёл несколько заблуждений, которые приводят к ошибкам. Заблуждения эти описаны много-много лет назад, но разработчики до сих пор часто забывают о них подумать.
Вот некоторые из них с заметками, которые я сделал:

  • Сетевые соединения надёжны.
    А случиться может что угодно: сгорит свитч, случайно перережут кабель, у провайдера будет накладка и т.д.
  • Задержки, связанные с передачей по сети – не проблема.
    Разработчики тестируют приложения локально, тестеры – на тестлабах. Скорости там могут быть до 1000 раз выше, чем в реальной жизни. Использование lazy loading может быть бомбой замедленного действия с точки зрения производительности.
  • Ширина канала – не проблема.
    Эффективная скорость, доступная на гигабитном канале – порядка 40 мегабайт в секунду. Это звучит не так круто, как “гигабит”.
    Если существуют столкновения между lazy loading и eagerly loading, то, возможно, речь должна идти о разных объектах?
  • Сеть хорошо защищена.
    Нет принципиально полностью защищённых систем. Если мы обещаем полностью защищённую систему заказчику – мы врём.
    В основном проблема не столько в сети/архитектуре/инфраструктуре, сколько в людях.
    Мы мало что можем с этим сделать.
    Направления куда смотреть: периодическая смена сертификатов/ключей, моральная готовность к тому, что рано или поздно что-то случится. Бизнес должен иметь a threat model analysis, legal plan, public relations plan, стейкхолдеры должны быть в курсе такой возможности.
  • Топология системы не изменится.
    Серверы умирают и добавляются, баллансировщики нагрузки – тоже, что-то уходит в облако, что-то переезжает в другую сеть.
    Клиенты меняют расположение и тип подключения (WiFi, 3G, LAN, WAN), могут появляться новые типы клиентов.
    Направления куда копать: не хардкодить адреса, делать конфигурацию максимально простой, автоконфигурирование/автонахождение.
  • Админ знает, что делать в случае чего.
    Сложные конфигурационные файлы в каждом углу не лучший помощник админа. Запланированный (upgrade) и незапланированный (rollback, errors) downtime. Количество девяток в availability: 5 девяток (99.999%) – это примерно 5 минут в год. 4 девятки – это час в год. Честно планировать и документировать availability.
    Успешно применяемое решение: непрерывное обновление без downtime’а. Пример: ebay, flickr, amazon и т.д. Flickr: обновление версии на продакшне в среднем каждые 8 минут.
    Условия такого решения: обратная совместимость.
    Проблема: дольше итерация разработки –> больше проблем с обратной совместимостью –> сложнее делать апгрейд без даунтайма –> бизнес откладывает апгрейд (важные кампании, презентации, клиенты, тендеры) –> дольше итерация разработки.
    И наоборот: короткая итерация –> меньше кода –> меньше проблем с обратной совместимостью –> проще апгрейд без даунтайма –> короче итерация.
  • Транспорт – не проблема.
    Сеть и CPU – это стоимость. Пример: сериализация/десериализация – это трата CPU. Использование “облачных” решений часто заставляет платить за CPU и тут отлично выплывает, насколько это влияет на стоимость.
    Решение: нет решения. Сеть и CPU всегда стоят денег.
  • Система атомарна и монолитна.
    Часто говорят: stateless сервера – это путь к scalability. На практике это часто может привести к тому, что всё состояние смещается в одно место – в базу данных. И уже база данных становится узким местом. А база данных – это то, что очень сложно масштабировать вширь. Stateless серверы – отличное решение, предлагаемое производителями огромных дорогих серверов БД и гридов вроде Кохеренса :)
    От себя: представитель Кохеренса на прошлогодней YOW говорил то же самое: чем больше вы фанатично делаете stateless логики, тем больше мы имеем денег на продаже серверов БД и грида. Сам слышал. Дословно: “Самый лучший способ сделать что-то немасштабируемым – сделать это stateless”.
  • Система закончена.
    Система никогда не заканчивается, пока жив бизнес, её использующий. Затраты на поддержку системы постоянны.
    Опытные разработчики создают новые фичи, джуниоры занимаются доработкой имеющегося кода и его поддержкой. Есть вопросы, почему многие системы со временем превращаются в один большой месс? :)
  • Бизнес-логика может и должна быть централизована.
    Бизнес-требования не одинаковы в смысле частоты и вероятности изменения.
    Повторное использование: термин, придуманный много лет назад, не работает. Больше повторного использования –> больше зависимостей –> сильнее связанность –> сложнее изменять/поддерживать систему –> риск сломать.
    Куда копать: Бизнес-логика часто понимается “в лоб”. Объект “Customer”, имеющий поля “Name” и “Status” вовсе не обязательно является целиковым объектом с точки зрения требований. Действительно ли нам нужно одновременно управлять именем и статусом? Или это совершенно разные операции с точки зрения бизнеса? Более того, даже в реальной жизни “клиент” не имеет “статуса”, “статус” – это какая-то информация О клиенте, которая хранится и управляется отдельно.
    Вопрос: почему мы пихаем это всё в один объект и потом пытаемся пользовать его везде, создавая лишние зависимости?
    Решение: Разделять бизнес-требования по смыслу и по частоте изменения оных. Получатся “вертикальные” “независимые” срезы системы: в одном мы управляем именем клиента, в другом – статусом.
    Изменения требований, связанных с одним “срезом” не влияют на поведение других.
    Такой “срез” можно называть “design package”, в идеале он может содержать в себе все те же слои: UI, BL, DB, etc.
    Такие “срезы” можно (это не обязательно делать) размещать на отдельных серверах, развёртывать и апгрейдить отдельно, без остановки всей системы. Все блокировки и транзакции – внутри одного “среза”, избегаем распределённых транзакций!
    Ответ на вопрос: Какие данные должны изменяться согласованно (consistently, я никогда не знаю, как перевести на русский!), вместо вопроса “какие объекты у нас есть чтобы их изменить?”

Потом ещё говорили о том, что такое coupled code, как это измеряется, что значат эти метрики, как их применять… Потом говорили о разных видах распределённости и взаимодейтвии…

Я плохо рассказываю, потому, что было гораздо интереснее и полезнее, чем я рассказываю :) Хотя это просто заметки, 2% от того, что и о чём говорилось…
В перерывах (коих у нас три: два по 15 минут и один часовой) люди очень активно обсуждают то, что говорится на тренинге, между собой, для кого-то это совсем новые концепты, кто-то имел дело, но сейчас представилось под другим углом и т.д. Люди очень обдумывают, высказывают, анализируют. Значит отдача есть.

Я, наверное, больше не буду писать таких постов, про то, что было. Много слишком, плотность большая.

А завтра будет ещё интереснее :)

P.S. А Udi действительно замечательный докладчик.

P.P.S. В субботу Udi делает ещё один концерт (полный день), в каком-то виде это сжатый вариант его трёхдневного курса по NServiceBus. Стоимость билета - $100. Многие задумались, а не пойти ли и туда ещё. Я тоже думаю. Завтра спросим его, насколько сильно то, что будет там, перекликается с тем, что будет за неделю у нас. Может и схожу, не так уж и дорого.

P.P.P.S. А завтра – вечерний концерт Udi от YOW! Nights. Стоимость $10. Туда многие наши пойдут :)

7/4/2011 1:51:09 PM

Вообще я хотел сегодня писать про первый день “архитектурной недели” – треннинга с Udi Dahan, но ещё до этого я хотел написать свои мысли на тему CQRS, в продолжение дискуссии (см. далее).
Написать я хотел именно до того, как на треннинге начнутся какие-то мысли по этому поводу, просто для того, чтобы выразить свои мысли на текущий момент. Правда, после первого дня они уже малость задеты треннингом, хотя никаких CQ(R)S мы пока и близко не касались…
В выходные, хм, были выходные, поэтому я хотел написать этот постинг сегодня в поезде, но в поезде утром не было свободных мест, а вечером я ехал назад не один, поэтому теперь придётся писать два постинга: этот и про впечатления от “первого дня” :)

Итак, пишу.

Недавно в Баззе произошла дискуссия о CQRS, начавшаяся тем, что не все понимают, что это такое, а заглохнувшая на том, что CQS, вроде бы, штука бесспорная и сегрегировать таки нужно, а вот CQRS с его бесполезным messaging-подходом – это нечто бесполезное, связанное с бесполезным “доменным дизайном”.
Разночтения остались как раз в этом "message-подходе”, предписываемом, в частности, CQRS, так что я решил остановиться на нём подробнее.

Итак, мы имеем подходы “мы сегрегируем команду с запросом, но не будем делать классы команд (messages), а будем просто дёргать методы сервисов” и “подход, с классом-командой (message) и классами-обработчиками (handlers).

Что мы имеем в случае дёргания сервисных методов:

Интерфейсы и классы сервисов (IMyService / MyService), содержащие набор методов для дёргания.
Нарушение правила “Single Responsibility”, в том случае, когда методы MyService содержат реализацию бизнес-логики (так как класс становится ответственным за много-много-много неопределённого чего.
Либо, если логики там нет и если зона ответственности этого класса – делегировать исполнение конкретному исполнителю – то этакий монстрообразный роутер-делегатор, который попадает в категорию “зачем писать то, что можно НЕ писать”.

Проблемы с командной работой – те самые check-out/edit/merge в случае, когда несколько человек работают над разной функциональностью, но вынуждены править один файл (а то и два – сервис и его интерфейс)

Проблемы с непониманием того, что происходит и ложью себе и заказчику
Очень часто разработчики считают, что если они делают обработку так или иначе “асинхронно” – то это “чревато” работой с потенциально устаревшими данными (запросил здесь, а ответ получил там), боятся ужасных слов “eventually consistent” и так далее. Поэтому они начинают врать себе и заказчику, что если они делают запрос к данным синхронно, то они имеют самые свежие данные “realtime”.
Они синхронно вызвают какой-нибудь Web/WCF/Rest сервис, тот лезет в базу, достаёт данные, производит (с помощью ORM) маппинг на классы, из этого формирует какой-то ответ, сериализует его в SOAP/XML/Json, отправляет по сети обратно, там какой-нибудь прокси принимает этот ответ, десериализует в какой-то объект, передаёт его клиенту и… Кто уже дал гарантию, что с момента “тот лезет в базу” информация не успела устареть?
За исключением критических случаев, когда используется какая-нибудь жуткая распределённая транзакция или locks на предмет “пока я не разрешу информацию менять нельзя”, которых всё равно никто не делает – никто таких гарантий не давал.
Никто и не даст таких гарантий. Их нет.
А тогда зачем врать? А если незачем, смотрим дальше.

Проблемы с ошибками.
Что происходит, если удалённый сервис, метод которого мы дёргаем, недоступен?
Что происходит, если сервис доступен, сделали вызов, но тут произошёл таймаут? Или пропала связь? Произошло это на этапе запроса или на этапе ответа? Иными словами, успел удалённый сервер выполнить обработку или нет?!
Что происходит, когда сервер доступен и всё в порядке, но вот база данных ответила ошибкой (например, произошёл deadlock, но об этом позже)?
Итого, практически во всех случаях мы имеем потерю данных. Плюс наша система начинает прямо-таки требовать того, чтобы обе части распределённого приложения всё время были доступны online. И хорошо, если их ещё всего две.

Что мы делаем с ошибками?
Ага, пишем в лог и показываем клиенту. А что если клиента уже нет? Пропала сеть или что-то ещё? А кто вообще читает этот лог?

Проблемы с производительностью.
Здесь интересно :)

Каждый такой “синхронный” (а асинхронность, кстати, проблем не уменьшает, а может и усугубить, если идёт постоянный polling) вызов сервиса обходится серверу в один обрабатывающий поток. Мы ведь говорим о больших и серьёзных приложениях, поэтому разумно предположить, что клиентов, “бомбардирующих” сервис своими запросами, много. Что происходит, когда сервер не может больше создать потоков? Клиент получает исключение The request was refused by server. Что мы делаем с исключениями мы уже обсудили.
Поток и его создание – штука дорогая. Например, тоже в Баззе обсуждали, на поток отводится стек в размере 1 мегабайта (по умолчанию). 100 потоков – 100 мегабайт только стека. Плюс всё остальное. Кончится память => исключение => тот же “The request was refused by server” на стороне клиента.
Что происходит, когда, скажем, IIS обнаруживает, что application pool вдруг жрёт немерено памяти? Он делает автоматический recycle этому application pool. Все потоки прерываются и умирают, пул поднимается, и вроде всё хорошо должно быть… Но recycle – операция не мгновенная. Запросы множества клиентов, которые IIS заботливо накопил за время сброса пула, начинают обрабатываться пулом после сброса… Много, много запросов… Создаётся много, много потоков… Пул “вдруг” начинает жрать много-много памяти… IIS “просекает фишку” и сбрасывает пул снова…

Когда в базу данных приходит команда обновить какие-то данные, она накладывает т.н. row lock на запись (или записи) с которыми работает. Но что происходит при большом количестве запросов? Я не уверен, как другие, а MS SQL Server выполняет процедуру, называемую “эскалацией локов”. В первом приближении её можно описать так: операция lock достаточно трудоёмка, большое количество локов нежелательно, поэтому вместо блокирования конкретной записи сервер базы данных начинает блокировать страницы.
А здесь вы уже поняли: другие запросы, обновляющие другие данные, которые по “счастливой” случайности находятся на тех же страницах, вынуждены становится в очередь и ждать.
Что не только приводит к тому, что потоки “умирают” не так быстро, как хотелось бы, но и, как уже стало понятно, к риску возникновения dead lock’ов. Просто потому, что один запрос может заблокировать одну страницу, другой – другую, а потом начать ждать друг друга.
И ведь сервер базы данных честно нам скажет: виноват я, сервер базы данных, я убью одну транзакцию-то, а ты “please try again later”. Кто-нибудь “try again later” в таком подходе? Скорее всего – см. предыдущий пункт :)

Проблемы с памятью.
А уж если мы должны сделать что-то вроде “открыть транзакцию, сохранить в базу данных, получить Id, вызвать сервис (передать ему этот Id), если всё прошло успешно – подтвердить транзакцию”… Обычное дело для тех, кто “имел опыт работы с большими системами” на практике (никого конкретно не имею в виду).
Но на самом деле тут целый мешок проблем.

1) Я уже упоминал: вызвали сервис, получили ошибку по таймауту. Отменять транзакцию? А вдруг сервер таки обработал данные? Подтверждать? Это уже смешно! :)

2) Транзакция – штука такая. Чем длиннее она – тем нам не легче. И тем больше шанс возникновения дедлока, особенно в связи с page locks. А с вызовом удалённого сервиса и ожиданием некого магического “статуса" или “войда” она короче точно не станет.

3) Наконец, сами проблемы с памятью! Мы набрали кучку какой-то информации, ORM поработал, что-то сохранили, что-то ещё нет, вызвали сервис, ждём. А вот пока ждём, есть ну очень большая вероятность того, что время, когда наши объекты “переживут” и нулевое, и первое поколение сборщика мусора много раз пройдёт. Много – в мире сборщика мусора. И вся наша промежуточная байда, с которой мы имели дело в контексте метода (включая и прокси сервисов, и объекты ORM) попадут во второе поколение.
Вот мы и получили “утечки памяти второго рода” :) Второе поколение сборщик будет чистить только тогда, когда памяти действительно останется мало, операция это достаточно напряжённая, долгая, поток (а в случае клиентского GC, то и все потоки) нужно остановить, чтобы дефрагментировать кучу…

А тут ещё множество клиентов бомбардируют своими запросами, на которые надо выделять стеки, потоки, всё это опять жрёт память.. Круг замкнулся :)

Чем нам поможет message-oriented подход:

Тем, что мы, помимо классов команд вводим несколько новых сущностей (и выводим кое-какие имеющиеся).

Мы вводим понятие ServiceBus, или MessageBus.
В простом варианте – это очередь куда сыплются наши сообщения.
Этим мы явно даём понять, что команда обрабатывается не “сейчас-сейчас-прямосейчас”, а когда-то.
Этим мы явно даём понять, что никакого ответа или статуса мы в ответ не ожидаем.
Этим мы явно даём понять, что нас не интересует, доступна ли в данный момент та часть системы, которой предназначается сообщение и, более того, можем себе это позволить!
Отправка сообщения теперь сводится в передаче его в MessageBus, по сути – пиханию в локальную очередь. Операция сущкственно более быстрая, нежели “прямой” вызов сервиса, поэтому шанс попасть во второе поколение очень серьёзно меньше.
Время транзакции, описанной мною выше, существенно меньше, причём это “настоящая” транзакция: если что-то пошло не так, то мы отменяем как запись в базу данных, так и запихивание сообщения в очередь, то есть, операция изменения данных и операция отсылки сообщения становятся на самом деле атомарны.
Оправка сообщений из очереди клиента в очередь сервера не будет бомбардировать сервер немереным количеством запросов, количество можно контроллировать и обойти эти проблемы.
В случаях, скажем, MSMQ или SQL Server ServiceBroker эта задача уже решена за нас.
На стороне сервера мы тоже можем использовать столько потоков для обработки команд из очереди, сколько нам нужно. Мы можем создать потоки один раз и переиспользовать их, если заходим.
Так, как у нас нет теперь огромного количества одновременных запросов к базе данных, количество page locks по отношению к row locks можно свести к минимуму.

На сладкое мы имеем возможность в случае, если что-то произошло не так, попробовать автоматически повторить исполнение команды: раз, другой, третий, с разными промежутками времени, как нам диктует бизнес-логика.
Мы становимся более толерантны к некоторым вещам вроде ненадёжности сети и т.д.
Если посмотреть на практики Windows Azure, то они прямо рекомендуют в случае подобных ошибок просто повторять запрос ещё раз, так как сервис, к которому идёт обращение, мог как раз в эту секунду находиться в режиме переключения (одна нода заменялась другой) и т.д.
В качестве компота: команды, которые даже после повтора не были обработаны, можно сохранить в отдельной очереди, или где-то ещё, а потом, после устранения ошибки (пофиксили баг, восстановили отрубленный сетевой кабель, подняли сервер), “проиграть” заново.

Мы выкидываем монстрообразные IMyService/MyService.
Вместо этого у нас появляется два интерфейса, которые крайне вряд ли придётся менять, скажем IMessage и IMessageHandler<T> where T: IMessage.
Разработчику теперь, чтобы добавить новую функциональность, обработать новое сообщение, нужно только создать класс сообщения и новый обработчик, в котором реализовать логику. Или исправить. Один выстрел – один труп, никаких merge, принцип single responsibility соблюдён. Наконец, зто просто гораздо легче сделать, нежели править интерфейс, править большой класс сервиса и т.д.

Мы получаем отличную возможность версионности – практически бесплатно!
Скажем, если мы решили сделать новую версию, что-то добавить в MyMessage. Так мы просто наследуем MyMessage2 : MyMessage и делаем обработчик MyMessage2. Теперь “старые” и “новые” клиенты будут работать одинаково.
Пойдите-ка, добавьте параметров в напрямую дёргаемые методы, поподдерживайте разные версии клиентов настолько легко и непринуждённо :)

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

6/28/2011 12:08:13 PM

Всю следующую неделю буду на курсах.

В Сидней приезжает Udi Dahan, человек, специализирующийся на SOA-архитектуре и распределённых системах, и написавший NServiceBus в частности.

Иду на курс, который называется “Advanced Distributed Systems Design using SOA & DDD”, его содержание можно увидеть здесь: http://www.udidahan.com/training/#Advanced_Distributed_System_Design

Кроме того, в понедельник Udi вечером даёт ещё один концерт, на котором будет рассказывать про этот самый NServiceBus. Это уже не часть курсов, а дополнительное событие, вход туда свободный (за символическую плату в $10), вроде бы собиралось пойти много “наших”, да и нет смысла не ходить-то.
Тоже буду там, разумеется.

В общем, заканчиваем на работе первую версию нашего собственного Service Bus, потом на курсы, получать новые идеи и знания – и обратно, применять всё это дело.

Жду.

2/24/2011 11:17:05 AM

Решили мы навести порядок с развёртыванием приложений на серверах. Точнее, решили-то давно, а вот, что называется, “руки доходить” начали только сейчас.

Основные тезисы:

  • Иметь в проекте несколько вариантов конфигурационных файлов, использующихся в разных окружениях (Local, UAT, Test, Staging, Production, etc) – неправильно, сложно и некрасиво. Сюда же как вариант относятся файлы трансформации, поддерживаемые VS2010 (*.Staging.Config, *.Prod.Config, *.UAT.config, etc).
    В идеале хотелось бы иметь один конфигурационный файл с максимум одной трансформацией для debug и release компиляци. Release-компиляция для всех окружений должна быть одинаковой.
  • Иметь где-то в проекте/почте/голове/на стене список, по которому развёртывается приложение – неправильно.
    Я имею в виду списки вида:
    1. Остановить сервисы такие-то;
    2. Обновить базу данных так-то;
    3. Скопировать в каталог веб-сайта то-то;
    4. Изменить/проверить конфигурацию и connection strings;
    5. Прочая байда…
  • Выполнять этот список наполовину вручную – неправильно Winking smile
  • Прописывать имена и пароли к базе данных и прочие значимые вещи в конфигурационные файлы и хранить их в lдоступном каждому source control – неправильно;
  • Ситуация, при которой разработчики имеют доступ/пароли к production environment – неправильная;
  • Если в случае формирования нового окружения, или в случае добавления каких-то серверов/компьютеров приходится делать долгое и утомительное конфигурирование новых машин перед тем, как установить на них приложение – это неправильно;

Хочется сделать более-менее правильно и удобно, поэтому остановились на использовании MSDeploy, который позволяет делать следующее:

  • Создавать самодостаточный инсталляционный пакет (в конечном итоге это zip-файл), либо набор таких пакетов;
  • Изменяющиеся от окружения к окруже��ию параметры могут быть переданы при развёртывания пакета как параметры;
  • Операторам, выполняющим развёртывание, не нужно “знать лишнего”, достаточно лишь знать на какую машину послать какой пакет (в случае, если их несколько), либо с какими параметрами устанавливать пакет на какой машине (в случае, когда всё упаковано в одном пакете), для чего они легко и привычно сделают скрипт, который смогут запускать одним нажатием клавиши;
  • Пароли от “настоящего” production environment никогда не попадут в source control, доступный разработчикам. IT-ребята могут, как они и любят, хранить файлики с параметрами в своих закромах, недоступных неверным;
  • MSDeploy позволяет автоматическое развёртывание пакетов без необходимости заходить и копировать что-то по FTP/SSH/RDP, включая опции типа “сделай везде как вот здесь” и т.д.;
  • Cоздание пакета MSDeploy может быть полностью автоматизировано, например, как часть билд-процесса;
  • Грамотно созданному пакету практически всё равно, устанавливаться ли “с нуля” на “чистую” машину, либо обновлять уже существующую версию, после развёртывания получаем работоспособную машину;
  • Уже созданные пакеты легко хранить, в любой момент можно найти и установить текущую, либо любую из предыдущих версий;

Вкратце, развёртывание должно быть простым и безболезненным, не должно требовать дополнительной подготовки и должно сводиться к “нажатию большой зелёной кнопки” техническим оператором, мы в любой момент готовы к развёртыванию.

На вопрос “что” мы уже ответили – MSDeploy.
Возможно я ещё напишу немного ответов на вопрос “как” (документация по MSDeploy отчего-то крайне невнятная и убогая).

Powered by BlogEngine.NET 2.5.0.6

About the author

Alexey Raga Alexey Raga
.NET software developer.

E-mail me Send mail

Twitter


Recent posts

Archive

Disclaimer

The opinions expressed herein are my own personal opinions and do not represent my employer's view in anyway.

© Copyright 2012

Sign in