Bank.MoveMoney
dim ctxObject As ObjectContext Set ctxObject = GetObjectContext() ... On Error GoTo ErrorHandler
dim objAccount As Bank.Account Set objAccount = ctxObject.CreateInstance("Bank.Account") ... objAccount.Post(lngSecondAccount, lngAmount) objAccount.Post(lngPrimeAccount, 0 - lngAmount) ... ctxObject.SetComplete ... ErrorHandler: ctxObject.SetAbort ...
Мы значительно сократили и упростили текст примера, оставив в нем иллюстрацию самых базовых моментов. Первое, на что нужно обратить внимание- это объект ctxObject, который носит название "контекст объекта". Каждый объект, размещенный в MTS, автоматически получает в соответствие такой объект на все время своей жизни. Контекст объекта управляет участием объекта в транзакциях под контролем MTS (см., например, методы SetComplete и SetAbort в примере) и предоставляет средства для программной проверки безопасности. Например, если поступает запрос на перевод крупной суммы, мы можем предусмотреть проверку на наличие у него соответствующих прав:
If (lngAmount > 500 Or lngAmount < -500) Then If Not ctxObject.IsCallerInRole("Managers") Then Err.Raise Number:=ERROR_NUMBER, ... Description:= "Need 'Managers' role for amounts over $500"
End If End If
Установка самих ролей происходит в MTS Explorer. Ccылку на контекст объекта можно получить с помощью функции MTS API GetObjectContext(). В данном примере мы создаем объект Account от контекста объекта MoveMoney, в результате чего он будет выполняться внутри той же транзакции, что и объект MoveMoney. В общем случае это зависит от транзакционного атрибута компоненты, под которым она установлена в MTS. Возможны 4 варианта: supported- при создании нового объекта его контекст наследует клиентскую транзакцию, если же клиент исполняется вне транзакции, то новый контекст тоже не будет транзакционным; required- то же, что и в предыдущем случае, но в случае отстутствия транзакции у клиента MTS откроет новую транзакцию для объекта; requires new- объект не наследует транзакцию клиента, для него всегда открывается новая транзакция; not supported- транзакция никогда не будет открыта объекту вне зависимости от наличия транзакции на клиенте.
Одной из отличительных черт MTS является чрезвычайно рачительное, я бы даже сказал, трепетное отношение к системным ресурсам. Его девизом является активизация по мере необходимости и как можно более быстрая деактивизация объектов. Когда MTS деактивизирует объект, тот не разрушается, а становится доступен для повторного использования другими объектами. По секрету скажу, что MTS зачастую наглеет настолько, что может деактивизировать созданный вами объект при живых клиентских ссылках. То есть если вы создали объект, а потом отошли кофейку попить, не удивляйтесь, что этот сквалыга уже спер ваш объект, деактивизировал его и загнал кому-нибудь на сторону. Сердиться на него за это не стоит, потому что когда вы вернетесь и скажете "эй, парень, гони взад мой объект", лучше подождать лишних пол-секунды, пока MTS создаст или утянет (что существенно быстрее) с очередного ленивого соединения такой же объект, чем получить сообщение типа "извини, брат, не могу- память кончилась". MTS деактивизирует объект при вызовах методов окончания транзакции SetComplete или SetAbort. В следующий раз при повторном обращении к этому объекту MTS по обыкновению вмешается в COMовский вызов, подсунет вам тот самый ваш (а может и не ваш) объект и вызовет у него затребованный вами метод. То же происходит при работе с базами данных. Так как одной из самых дорогих операций является установка соединения, то MTS склонен держать у себя пул ODBC-соединений, и если нужное вам соединение уже туда попало и является свободным, как вы думаете, что сделает MTS, когда вы его попросите соединиться с базой? Кинется бегом открывать новое соединение? Щас прям. В подавляющем большинстве случаев вы получите его из пула. Смех смехом, однако бережное расходование системных ресурсов является одним из необходимых условий высокой масштабируемости, и не обладай MTS такими возможностями, он едва ли сумел бы на обычной в общем-то конфигурации без особых наворотов, обработать миллиард транзакций в сутки.
Иногда возникают ситуации, когда объект вынужден вернуть управление клиенту не будучи готовым завершить транзакцию.
Пусть у нас есть объект, отвечающий за ввод одной бухгалтерской проводки. Нам нужно ввести серию проводок, скажем, за день и сверить сумму с итогом. Если они совпадают, то только тогда всю группу проводок можно фиксировать как единую транзакцию. Для этих целей контекст объекта имеет метод DisableCommit, который запрещает деактивизацию и сброс внутреннего состояния объекта. В нашем случае его должен вызвать объект ввода проводки. После выверки итогов объектом вызывается метод EnableCommit с последующей фиксацией или откатом транзакции.
Итак все объекты, работающие в среде управления MTS, могут вызывать друг друга. В отличие от них базовым клиентом называется истинный клиент в рассмотренной нами в п.3 трехуровневой схеме, т.е. это приложение, работающее, очевидно, не под управлением MTS, основная цель которого обеспечивать пользовательский интерфейс и отображать запросы пользователя в вызовы компонент. Для управления работой объектов MTS базовый клиент использует объект TransactionContext. Несмотря на то, что схема его применения очень похожа на контекст объекта (методы CreateInstance, Complete и Abort), базовый клиент может только управлять транзакцией MTS, но не участвовать в ней. Например, он не может напрямую открыть соединение с базой данных и вставить операции над ней внутрь этой транзакции. Наверное, это правильно, потому что коль скоро бизнес-логика ушла из клиентской части, то участие в транзакциях middleware недопустимо для базового клиента.
VB4 умел создавать только однопоточные компоненты. MTS обеспечивает их поддержку, хотя это самый медленный способ, чреватый взаимными блокировками. Предположим, объекты А и В последовательно исполняются на одном потоке управления. А блокирует запись Х и заканчивает работу, забыв сделать SetComplete. Управление получает объект В, которому тоже нужна запись Х, поэтому он простаивает в ожидании, пока А ее разблокирует. Но для этого А должен получить поток управления, который наглухо занял В. Имеем картину под названием "Приплыли".
В VB5 можно создавать apartment- threaded компоненты, которые также поддерживает MTS, что уже легче. Каждому объекту внутри компоненты назначается отдельный поток на все время его жизни. Границы апартаментов определяются деятельностью (activity), которая, насколько я понимаю, означает "гирлянду" объектов, связанных последовательными вызовами. Таким образом, если два объекта принадлежат разным деятельностям, они могут выполняться параллельно независимо от того, принадлежат они одной или разным компонентам. Мне кажется, что логично было бы ввести в последующих версиях MTS модель рабочих потоков, так чтобы объекты не привязывались жестко к какому-то потоку, а могли свободно между ними перераспределяться и при этом не требовался бы кросс-поточный маршалинг.
Административными единицами при размещении компонент в MTS служат пакеты. Считается, что компоненты внутри пакета полностью доверяют друг другу, поэтому взаимные права компонент в пакете не проверяются, кроме того они запускаются в одном процессе. Очевидно, что базовые вызовы имеют identity клиента, поэтому при доступе к компонентам внутри пакета проверяется user-id. Вызовы, идущие из пакета, уже имеют identity данного пакета, поэтому если пользователи обращаются к компонентам, а компоненты, в свою очередь,- к базе данных, то имеет смысл сгруппировать компоненты по пакетам в соответствии с правами пользователей на доступ к базе, в противном случае авторизацию придется прописывать ручками внутри компонент. Это своего рода издержки переходного периода к многоуровневым системам. Перестройте свое мышление в соответствии с новым подходом: вы уже выросли из системы "клиент-сервер", давайте права на базу, имея в виду не конечных пользователей, а компоненты. В конце концов, пользователь вообще теперь не работает с базой- это забота компонент. Администрирование пользовательского доступа к компонентам осуществляется из MTS Explorer.
MTS допускает введение внутри пакетов глобальных переменных, доступ к которым разрешен для каждой компоненты.
Глобальные переменные (свойства) организованы в группы, получить или создать которые можно с помощью SharedPropertyGroupManager. Аналогично внутри каждой группы можно получить или создать разделяемое свойство при помощи SharedPropertyGroup. Вернемся к нашему банковскому примеру в начале пункта. Предположим, что мы еще хотим протоколировать каждую проводку в журнале и присваивать ей согласованный уникальный номер. Вот как для этого могут использоваться разделяемые свойства:
dim spmMgr As SharedPropertyGroupManager Set spmMgr = CreateObject("MTxSpm.SharedPropertyGroupManager.1")
dim spmGroup As SharedPropertyGroup dim bResult As Boolean Set spmGroup = spmMgr.CreatePropertyGroup("Receipt", LockSetGet, _ Process, bResult)
dim spmPropNextReceipt As SharedProperty Set spmPropNextReceipt = spmGroup.CreateProperty("Next", bResult)
dim spmPropMaxNum As SharedProperty Set spmPropMaxNum = spmGroup.CreateProperty("MaxNum", bResult)
dim objReceiptUpdate As Bank.UpdateReceipt If spmPropNextReceipt.Value >= spmPropMaxNum.Value Then Set objReceiptUpdate = ctxObject.CreateInstance("Bank.UpdateReceipt") spmPropNextReceipt.Value = objReceiptUpdate.Update(strResult) spmPropMaxNum.Value = spmPropNextReceipt.Value + 100 End If
' Get the next receipt number and update property
spmPropNextReceipt.Value = spmPropNextReceipt.Value + 1