Клас віддаленого проксі — це не (дуже) боляче

Fish Out Of Watermelon by Joan Pollak (Динамічна диспетчеризація поспішає на допомогу)
Після кількох статей про MapReduce нам здалося необхідним ще раз відійти в сторону і поговорити про інфраструктуру, яка допоможе полегшити побудову рішення MapReduce. Ми, як і раніше, говоримо про InterSystems Caché, і, як і раніше, намагаємося побудувати MapReduce систему на базі наявних в системі підручних матеріалів.
На певному етапі написання системи, типу MapReduce, постає завдання зручного виклику віддалених методів і процедур (наприклад, здійснення керуючих повідомлень з контролера на бік керованих вузлів). У середовищі Caché є кілька простих, але не дуже зручних методів досягти цієї мети, тоді як хочеться отримати саме зручний.

Хочеться взяти простий й послідовний код, тут і там викликає методи об'єкта або класу, і чарівним помахом руки зробити його працюючим вже з віддаленими методами. Звичайно ж, ступінь "віддаленості" може бути різною, наприклад, ми можемо просто викликати методи в іншому процесі того ж самого сайту, суть від цього сильно не зміниться — нам потрібно отримати зручний спосіб маршаллизации викликів "на ту сторону" зовні поточного процесу, що працює в поточної області.
Після декількох невдалих спроб автор раптом усвідомив, що в Caché ObjectScript є дуже простий механізм, який дозволить приховати всі низькорівневі деталі під зручною, високорівневої оболонкою — це механізм динамічної диспетчеризації методів і властивостей.
Якщо озирнутися (далеко) назад, то можна побачити, що починаючи з Caché 5.2 (а це на хвилиночку з 2007 року) в базовому класі
%RegisteredObject
є кілька визначених методів, успадкованих кожним об'єктом у системі, які викликаються при спробі виклику невідомого під час компіляції методу або властивості (зараз ці методи переїхали в інтерфейс
%Library.SystemBase
але це істотно не змінило суті) .










Ім'я
Method %DispatchMethod (Method As %(String Args...)
Дзвінок невідомого методу або доступ до невідомого багатовимірний властивості (їх синтаксис ідентичний)
ClassMethod %DispatchClassMethod (Class As %String, Method As %(String Args...)
Дзвінок невідомого методу класу для заданого класу
Method %DispatchGetProperty (Property As %String)
Читання невідомого властивості
Method %DispatchSetProperty (Property As %String, Val)
Запис в невідома властивість
Method %DispatchSetMultidimProperty (Property As %String, Val, Subs...)
Запис в невідоме багатовимірне властивість (не використовується в даному випадку, буде частиною іншої історії)
Method %DispatchGetModified (Property As %String)
Доступ до прапора "modified" ("змінено") для невідомого властивості (також не використовується в даній історії)
Method %DispatchSetModified (Property As %String, Val)
Доповнення до методу вище запис прапор "modified" ("змінено") для невідомого властивості (не використовується в даній історії)
Для простоти експерименту, ми будемо використовувати тільки функції, що відповідають за виклик невідомих методів і скалярних властивостей. У продуктовій середовищі вам на певному етапі може знадобитися змінити всі або більшість описаних методів, т. ч. будьте пильні.
Спочатку простіше — протоколирующий об'єкт-проксі
Нагадаємо, що ще з часів "царя Гороха" в стандартній бібліотеці CACHELIB були стандартні методи і класи для роботи з проекцією JavaScript об'єктів в XEN
%ZEN.proxyObject
він дозволяв маніпулювати динамічними властивостями навіть у часи, коли ще не було робіт по документної базі DocumentDB (не питайте) і тим більше не було нативної підтримки JSON об'єктів в ядрі середовища Caché.
Давайте, для затравки, спробуємо створити простий, протоколирующий всі виклики, проксі об'єкт? Де ми обернем всі виклики через динамічну диспетчеризацію із збереженням протоколу про кожну подію, що сталася. [Дуже схоже на техніку mocking в інших мовних середовищах.]
[[Як це переводити на російську? "мОкать"?]]
В якості прикладу візьмемо сильно спрощений клас
Sample.SimplePerson
(за дивним збігом обставин дуже схожого на
Sample.Person
з області SAMPLES в стандартній поставці :wink: )
DEVLATEST:15:23:32:MAPREDUCE>set p = ##class(Sample.SimplePerson).%OpenId(2)

DEVLATEST:15:23:34:MAPREDUCE>zw p

p=<OBJECT REFERENCE>[1@Sample.SimplePerson]
+----------------- general information ---------------
| oref value: 1
| class name: Sample.SimplePerson
| %%OID: $lb("2","Sample.SimplePerson")
| reference count: 2
+----------------- attribute values ------------------
| %Concurrency = 1 <Set>
| Age = 9
| Contacts = 23
| Name = "Waal,Nataliya Q."
+-----------------------------------------------------

тобто маємо персистентный клас — з 3-ма простими властивостями: Age, Контакти і Name. Обернем доступ до всіх властивостей цього класу і виклик всіх його методів у своєму класі
Sample.LoggingProxy
, і кожен такий виклик або доступ до властивості будемо протоколювати… куди-небудь.
/// Простий протоколирующий проксі об'єкт:
Class Sample.LoggingProxy Extends %RegisteredObject
{
/// Кладемо лог доступу в глобал
Parameter LoggingGlobal As %String = "^Sample.LoggingProxy";
/// Зберігаємо посилання на відкритий об'єкт 
Property OpenedObject As %RegisteredObject;

/// просто зберігаємо рядок як наступний вузол в глобале
ClassMethod Log(Value As %String)
{
#dim gloRef = ..#LoggingGlobal
set @gloRef@($sequence(@gloRef)) = Value
}

/// Більш зручний метод з префіксом і аргументами
ClassMethod LogArgs(prefix As %(String args...)
{
#dim S as %String = $get(prefix) _ ": " _ $get(args(1))
#dim i as %Integer
for i=2:1:$get(args) {
set S = S_","_args(i)
}
do ..Log(S)
}

/// відкрити примірник іншого класу з заданим %ID
ClassMethod %CreateInstance(className As %String, %ID As %String) As Sample.LoggingProxy
{
#dim wrapper = ..%New()
set wrapper.OpenedObject = $classmethod(className, "%OpenId", %ID)
return wrapper
}

/// запротоколювати передані аргументи і передати управління через проксі посилання
Method %DispatchMethod(methodName As %(String args...)
{
do ..LogArgs(methodName, args...)
return $method(..OpenedObject, methodName, args...)
}

/// запротоколювати передані аргументи і прочитати властивість через проксі посилання
Method %DispatchGetProperty(Property As %String)
{
#dim Value as %String = $property(..OpenedObject, Property)
do ..LogArgs(Property Value)
return Value
}

/// запротоколювати передані аргументи і записати властивість через проксі посилання
/// log arguments and then dispatch dynamically property access to the proxy object
Method %DispatchSetProperty(Property Value As %String)
{
do ..LogArgs(Property Value)
set $property(..OpenedObject, Property) = Value
}

}

  1. Параметр класу
    #LoggingGlobal
    задає ім'я глобал, де будемо зберігати лог (в даному випадку в глобале з ім'ям
    ^Sample.LogginGlobal
    );
  2. Є два простих методу
    Log(Arg)
    та
    LogArgs(prefix, args...)
    які пишуть протокол глобал, заданий властивістю вище;
  3. %DispatchMethod
    ,
    %DispatchGetProperty
    та
    %DispatchSetProperty
    обробляють відповідні сценарії з викликами невідомого методу або звернення до властивості. Вони протоколюють через
    LogArgs
    кожен випадок звернення, а потім безпосередньо викликають метод або властивість об'єкта з посилання
    ..%OpenedObject
    ;
  4. Також там задано метод "фабрики класу"
    %CreateInstance
    , який відкриває примірник заданого класу по його ідентифікатору
    %ID
    . Створений об'єкт "обертається" в об'єкт
    Sample.LogginProxy
    , посилання на якого і повертається з цього методу класу.

    Ніякого чаклунства, нічого особливого, але вже в цих 70 рядках Caché ObjectScript ми спробували показати шаблон виклику методу/властивості з побічним ефектом (більше корисний приклад такого шаблону буде показано нижче).
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.