Стратегії кешування Web-сервісів XML

Незважаючи на зростання швидкостей роботи процесорів і мереж,
продуктивність залишається основною турботою розробників додатків. А
значить, незалежно від того, пишете ви Web-сервіс на основі XML (XML
Web Service), завантажуєте бітові карти зображень в пам'ять відеоплати
або навіть проектуєте який-небудь приголомшливий чіп нового покоління, вам
безумовно варто подумати про універсальний механізм підвищення
продуктивності: про кешуванні.

Введення

Незважаючи на зростання швидкостей роботи процесорів і мереж,
продуктивність залишається основною турботою розробників додатків. А
значить, незалежно від того, пишете ви Web-сервіс на основі XML (XML
Web Service), завантажуєте бітові карти зображень в пам'ять відеоплати
або навіть проектуєте який-небудь приголомшливий чіп нового покоління, вам
безумовно варто подумати про універсальний механізм підвищення
продуктивності: про кешуванні.

У цьому випуску рубрики At Your Service буде розглянуто, як ви,
розробник і споживач Web-сервісів, можете задіяти
переваги кешування. Ми обговоримо кешування на рівні додатку в
ASP.NET, а також кешування в HTTP та його застосування до Web-сервісів XML.
Нарешті, ми подумаємо, як використовувати приклад сервісу Discovery
вигаданої компанії MSDN Pencil Company, і реалізувати для нього
стратегію кешування, що має сенс при щоденному оновленні каталогу
продукції цієї компанії.

 

На що звертати увагу при виборі варіантів кешування

І творець, і споживач Web-сервісу може реалізувати самі
різноманітні варіанти кешування. Однак не всі механізми реалізації
кешу ефективно збільшують продуктивність або хоча б створюють
відчуття, ніби вона підвищилася. Вам слід проаналізувати, що має
сенс у вашому конкретному випадку. Ось кілька запитань, на які ви
повинні самі собі відповісти, вибираючи стратегію кешування Web-сервісу.

Чи багато у мене динамічних даних?

Часи, коли вважалося, що кешування – це завжди добре,
пройшли. Так, якщо Web-сервіс постійно повертає різні дані,
кешування навряд чи вам допоможе. Але тільки з того, що дані
динамічні, зовсім не випливає, ніби про кешуванні треба забути. Якщо
хоча б частина відповіді сервісу щодо статична, кешування здатне
підвищити продуктивність вашого Web-сервера. Візьмемо випадок, коли
інформація змінюється, але не для кожного запиту. Наприклад, якщо ви
щомиті отримуєте сотні запитів до сервісу, повідомляють інформацію про
погоді, то, напевно, захочете у відповідь на більшість запитів посилати
дані з кешу, а його оновлення виконувати, скажімо, раз на 5 хвилин.

Не конфіденційні чи це дані?

Часто Web-сервіси XML мають справу з даними, специфічними для
конкретного користувача. Це зменшує користь від кешування, але не
відмовляйтеся від нього лише тому, що ви працюєте з такими даними.
Скажімо, якщо число користувачів Web-сервісу невелика, може виявитися
розумним кешувати інформацію для кожного користувача індивідуально –
особливо якщо користувачі неодноразово запитують ту ж
інформацію. І навіть якщо це не так, може існувати загальний примірник
класу, до якого відбувається звернення при кожному запиті від якогось
користувача. Проте будьте обережні при кешуванні конфіденційної
інформації, оскільки помилки в коді такого роду можуть призвести до її
розкриттю. З точки зору безпеки слід ввести обмеження
доступу.

Чи використовує Web-сервіс ресурси, загальні для декількох запитів?

Кешування застосовується не тільки до відповідей, а й до будь-яких даних або
ресурсів програми, що нерідко дозволяє значно збільшити
продуктивність. Наприклад, може виявитися розумним зберігати один і
той самий набір даних (dataset) для обслуговування декількох запитів.
Дані у відповіді варіюються залежно від конкретних запитів до
набору даних, але сам набір може залишатися одним і тим же для
безлічі запитів.

Чи можна передбачити майбутню потребу в ресурсах?

Подумайте про сценарії застосування вашого Web-сервісу. Чи можете ви
передбачити характер його використання? Припустимо, Web-сервіс дозволяє
споживачам знаходити якусь статтю, а потім викачувати її. У цьому випадку
справедливо припустити, що слідом за успішним пошуком статті піде
запит на її скачування. Web-сервіс може завчасно почати
потенційно тривалий процес завантаження статті в пам'ять (з файлу або
бази даних), і тоді до моменту надходження запиту на скачування статті
все буде готово.

Де кешувати дані Web-сервісу?

Найчастіше правильну відповідь на це питання: де завгодно. Але які
варіанти їх кешування? Щоб відповісти на це питання, розглянемо
потенційно можливий сценарій роботи Web-сервісу, наведений на рис.
1.

Браузер кінцевого користувача

HTTP-проксі

Web-сервер

Web-сервіс

Внутрішній Web-сервіс

SQL-сервер

Рис. 1. Можливі варіанти кешування для одного з сценаріїв роботи
Web-сервісу XML

Як бачите, кінцевий користувач звертається до сайту (він показаний у
жовтому прямокутнику), не знаючи, що цей сайт знаходиться за HTTP-проксі.
Потім Web-сервер посилає SOAP-запит до Web-сервісу в іншій організації
(В зеленому прямокутнику). SOAP-запит теж проходить через HTTP-проксі.
Після цього перший Web-сервіс переправляє запит іншому, внутрішньому
Web-сервісу, який запитує необхідні дані у Microsoft ® SQL
Server і нарешті повертає відповідь. SQL-дані використовуються при
підготовці відповіді від внутрішнього Web-сервісу, а на основі його відповіді
створюється відповідь від першого Web-сервісу. На сайті з відповіді Web-сервісу
формується HTML-сторінка, яка і повертається браузеру кінцевого
користувача.

Запит і відповідь проходять через безліч проксі-серверів і
маршрутизаторів. То де ж кешувати дані відповіді? У цьому сценарії –
на кожному етапі. SQL-сервер може кешувати результати запиту до
сервісу, внутрішній Web-сервіс – результати SQL-запиту, перший
Web-сервіс – результати внутрішнього Web-сервісу (так само як і HTTP-проксі
в "зеленій" організації), Web-сервер – відповідь Web-сервісу, проксі-сервер
в "жовтій" організації – відповідь Web-сервера, а браузер кінцевого
користувача – HTML-сторінку.

Коли закінчується термін зберігання даних?

При розробці стратегії кешування необхідно відповісти на одне з
найважливіших питань: коли видаляти дані з кеша. Іноді це зробити
дуже просто, так як процес, оновлюючий дані, запускається через
регулярні проміжки часу. Однак в інших ситуаціях дані
оновлюються через довільні інтервали. Тоді треба з'ясувати
оптимальний термін, після закінчення якого слід оновлювати кеш, дотримуючись
баланс між ризиком повернення з кеша застарілих даних і зростанням
продуктивності, що досягається за рахунок застосування кеша. Визначивши такий
інтервал, включіть цю інформацію в дані, щоб кешуючий системи
могли відповідно оновлювати вміст кешів.

Як повідомляти споживачів Web-сервісу про те, що дані
застаріли?

Відповідь на це питання залежить від способу кешування. Клієнтським
додаткам часто має сенс кешувати дані. У цьому випадку
клієнтські додатки повинні бути своєчасно поінформовані про
застарівання даних. Для цього, ймовірно, буде потрібно включати в
повертаються дані відомості про термін їх життя. Для Web-сервісу це може
означати додавання до XML-відповіді спеціального поля, де вказується
"Термін життя".

Вбудовані механізми кешування зазвичай включають в себе засоби для
завдання термінів старіння. У випадку з HTTP-кешуванням ці відомості
поміщаються в HTTP-заголовки, доступні проксі-серверів і клієнтським
системам. У ASP.NET є клас кешу, в який можна записувати дані.
При цьому у вас є можливість вказувати, коли дані слід видаляти
з кеша.

Чи можна покладатися на те, що дані будуть в кеші?

Якщо коротко – ні. Майже в будь-який механізм кешування вбудований
алгоритм видалення застарілий інформації з кеша. Дані зазвичай видаляються
з-за старіння, але іноді й через те, що до них давно не
зверталися, при цьому в кеш завантажуються інші дані, адже його ємність
обмежена. Так що більшість механізмів кешування не гарантує
наявності даних в кеші. Це тим більше вірно у відношенні механізмів
кешування в мережах (shared caching mechanisms) начебто кешей HTTP-проксі
або навіть ASP.NET.

Що буде, якщо споживачі Web-сервісу не скористаються
кешовані даними?

Існує кілька причин, по яких потрібні дані можуть не
потрапити в кеш. Наприклад, дані з більш високим пріоритетом здатні
просто витіснити ваші дані з кеша. У такому випадку розробник,
писав код для доступу до вашого Web-сервісу, мабуть, не
подбав про повторному використанні вже отриманих даних. Проектуючи
Web-сервіс, пам'ятайте про можливість збільшити продуктивність за рахунок
кешування, але передбачайте ситуації, в яких дані не потраплять до
кеш. Ви повинні вміти справлятися з такими ситуаціями, при яких
кешування працює неоптимально.

 

Сценарії кешування

Тепер, коли ми розглянули деякі питання, які потребують відповіді для
оцінки можливостей кешування, обговоримо, які ці можливості для
розробників Web-сервісів XML. Спочатку ми поговоримо про два підходи до
кешуванню: на прикладному рівні і на рівні протоколу. Потім перевіримо,
що пропонує ASP.NET для реалізації кешування на обох рівнях.

Кешування на прикладному рівні

Словом "додаток" (application) у наш час дуже
зловживають. У цій статті під терміном "додаток" я розумію
"Прикладний рівень" (application level), і він відноситься як до
Web-сервісу, так і клієнту цього сервісу, тобто до тих областях, де
розробник пише код, швидкодія якого чутливо до
кешуванню.

Отже, "кешування на прикладному рівні" відноситься і до коду
Web-сервісу, і до коду клієнта, який теж так чи інакше кешує
дані. Кешування в Web-сервісі може полягати у збереженні в
пам'яті комп'ютера примірників повторно використовуваних класів або не
мінливих даних.

На рівні клієнтського застосування кешування – це збереження відповіді
Web-сервісу для того, щоб клієнтові не доводилося посилати ще один
запит для отримання тих же даних.

Підтримка кешування звичайно пов'язана з наданням відомостей про
терміні життя даних. Якщо цей термін завжди постійний, його можна
документувати і "зашити" в код клієнта. Тоді вказувати його у відповіді
Web-сервісу не буде потрібно. Однак у багатьох випадках термін життя даних
мінливий, і відповідні відомості потрібно зберігати разом з кешованим
даними. При кешуванні на прикладному рівні в дані можна включити
нове поле, що містить параметр – термін життя. Оскільки зазвичай термін життя
– Це фактично метаінформація, що описує дані, відповідне місце
для її зберігання цих відомостей – елемент SOAP-заголовків. Зберігати
метаінформацію про SOAP-повідомленні слід саме там.

HTTP-кешування

HTTP надає ефективні механізми кешування. У його
специфікації описується, за якими правилами системні сервіси повинні
кешувати дані. В основному HTTP-проксі і клієнтські комп'ютери
забезпечують кешування без будь-яких зусиль з боку розробника
додатків, що використовують HTTP. Але область застосування HTTP-кешування
для Web-сервісів обмежена.

В даний час для звернення до Web-сервісів використовуються
SOAP-повідомлення, що поміщаються в тіло HTTP-запиту POST. На відміну від
HTTP-запитів GET, тіло POST-запитів виходить за область дії HTTP.
Тому реалізації протоколу HTTP в проксі-серверах і клієнтів не
здатні визначати, як кешувати відповіді на HTTP-запити POST. ASP.NET
підтримує виклик Web-методів через GET-запити, але цей механізм у
основному призначений для налагодження і не підтримується більшістю
інших інструментальних засобів SOAP.

Можливості кешування в ASP.NET

Одна з приємних особливостей розробки Web-сервісів XML в середовищі
ASP.NET – переваги великої функціональності, якої вже користуються
розробники додатків Web Forms. Вбудована функціональність ASP.NET
полегшує кешування Web-сервісів XML. Як раз зараз Роб Ховард (Rob
Howard) у своїй колонці

Nothing But ASP.NET розпочав цикл статтею, присвячених кешуванню в
цьому середовищі. Щоб краще зрозуміти специфіку ASP.NET з точки зору
кешування, ознайомтеся з цими статтями. Я ж зосереджуся на
механізмах, які допомагають в написанні Web-сервісів.

По суті, в ASP.NET існують три різних підходи до кешуванню:
кешування вихідних даних ASP.NET, HTTP-відповідей та додатків ASP.NET.
При кешування вихідних даних розробник повідомляє ASP.NET, що
результат запиту до конкретної сторінки можна повертати у відповідь на всі
подальші запити до цієї сторінки. Для подальших запитів
ASPX-сценарій не виконується – замість цього повинен бути відшкодований
результат попереднього запиту. У кеш вихідних даних можна помістити як
всю сторінку цілком, так і окремі елементи управління ASP.NET.
Існують механізми для зазначення строку життя кешованих даних, так само
як і механізми для кешування різних уявлень сторінки в
залежно від вхідних даних Web-форм.

Щоб налаштувати кешування вихідних даних ASP.NET для Web-сервісів,
додайте параметр CacheDuration до атрибуту WebMethod у визначенні
Web-методу. Цей параметр визначає час (у секундах), протягом
якого відповідь зберігається в кеші вихідних даних. Наступний фрагмент коду
демонструє, як помістити дані в кеш на 60 секунд.

 

<WebMethod(CacheDuration:=60)> _
Public Function HelloWorld() As String
    Return "Hello World"
End Function

На відміну від кешування вихідних даних ASP.NET кешування
HTTP-відповідей – просто спосіб, яким ASP.NET дозволяє налаштувати
HTTP-заголовки так, щоб клієнт і проксі-сервери знали, як кешувати
повертаються їм HTTP-відповіді. Для цього застосовується клас
HttpCachePolicy. У коді Web-сервісу він доступний з
Context.Response.Cache, але, як уже говорилося, можливості його
застосування до SOAP-запитах в тілі POST-запитів обмежені.

Третя форма кешування в ASP.NET реалізована у вигляді вельми
цікавого класу Cache. Не плутайте класи HttpCachePolicy і Cache –
навіть незважаючи на те, що їх батьківські класи посилаються на обидва цих
властивості як на "cache". Клас Cache доступний прямо з класу
HttpContext Web-сервісу. Він надає базову підтримку кешування
для ASP.NET-додатка. У кеші можна зберігати будь-які дані з його набору
(Collection). У багатьох відношеннях цей клас схожий на клас
HttpApplicationState, який зберігає глобальні дані програми у своєму
наборі.

Однак на відміну від останнього для класу Cache дозволяється задавати
критерій старіння його даних. Приміром, можна вказати, що об'єкт,
збережений у класі, застаріє в певний час. Допускається
встановлювати і такі обмеження, при яких об'єкт видаляється з кешу,
якщо до нього давно не було ніяких звернень. Можна навіть встановити зв'язок
між кешовані елементами і файлами – тоді при зміні файлу
відповідний об'єкт був видалений з кеша. При наступному зверненні до
кешу цього елемента в ньому не буде, і вам доведеться оновити дані –
швидше за все на основі нової інформації у зв'язаному файлі. І звичайно,
як і будь-який інший "законний" механізм кешування, клас Cache
реалізує засоби для видалення неактивних елементів при браку
ресурсів.

 

Dim Foo as New MyFooClass()
Context.Cache.Insert("foo", _
                     Foo, _
                     Nothing, _
                     DateAdd(DateInterval.Minute, 30, Now()), _
System.Web.Caching.Cache.NoSlidingExpiration)

Функція Insert підтримує кілька варіантів додавання даних в кеш.
Перший аргумент, "foo", – це ключ для посилання на наш об'єкт в наборі.
Другий параметр представляє сам елемент, що поміщається в кеш. Третій –
встановлює залежності начебто файлової, про яку ми вже згадували. У
нашому випадку жодних залежностей немає, так що цей параметр дорівнює
"Nothing". Наступний параметр явно задає термін старіння елемента в
кеші. Викликом функції DateAdd ми вказали, що об'єкт застаріє через 30
хвилин. Нарешті, останній параметр задає "ковзне" (sliding)
застарівання. Ковзаюче старіння означає, що кешированний елемент
видаляється, якщо до нього не звертаються протягом зазначеного проміжку
часу. Так як ми явно встановили термін життя даних (30 хвилин), цьому
параметру привласнюється значення NoSlidingExpiration.

 

Кешування каталогу MSDN Pencil Company

Тепер розглянемо конкретний приклад і визначимо стратегію кешування
в цьому сценарії. В останній статті рубрики At Your Service Скотт
визначив низку змін в інтерфейсі PencilDiscovery MSDN Pencil
Company, так що тепер можна запитувати весь каталог, не примушуючи
користувачів по кілька разів виконувати пошук. Мета цього рішення –
дозволити "розумним" клієнтським застосуванням кешувати весь каталог та
забезпечити можливість запитів до даних. Це знизить навантаження на наш
сервіс за рахунок зменшення запитів і, крім того, надасть
додаткову інформацію клієнтам, які користуються нашим сервісом. Ми
вирішили, що в нашій реалізації ми швидше за все будемо оновлювати дані
раз на день, додаючи в каталог нові олівці або видаляючи ті, яких
більше немає на складі.

З точки зору вибору стратегій кешування, рішення цього завдання
спрощується завдяки кількох моментів. По-перше, дані
загальнодоступні, тобто нам не потрібно турбуватися про те, щоб у різних
користувачів були різні уявлення даних. По-друге, нам відомо
точний час, коли дані оновлюються, а значить, можна чітко вказати
термін їх життя.

Тепер виберемо параметри кешування прикладного рівня. З клієнтської
сторони з цим особливих проблем немає. Клієнтський додаток запитує
дані раз на день і виконує до них будь-які запити, поки не настане
наступного дня. Але одна проблема все ж таки є: повідомлення клієнтського
програми про термін життя даних.

Один з варіантів вирішення цієї проблеми – просто документувати, що
клієнтське додаток повинен оновлювати дані кожні 24 години, але тоді
з'являються тимчасові вікна (максимальна тривалість яких – 24
години), протягом яких дані клієнта можуть відрізнятися від даних
Web-сервісу. Крім того, ми визначаємо інтерфейс, який можуть
реалізувати різні компанії, і не виключено, що в якійсь компанії
вирішать оновлювати дані щотижня, а не щодня.

Вихід простий: включати інформацію про термін життя даних у відповідь. У
визначення інтерфейсу Скотт
включив елемент ValidUntil (до розділу оголошення типів каталогу
олівців). Ми скористаємося цим полем для зазначення строку старіння
даних каталогу. Крім того, включивши інформацію про час в
SOAP-повідомлення, ми отримаємо додаткову перевагу – підтримку
кешування даних, навіть якщо вони передаються не по HTTP, а по іншому
протоколу. Наприклад, можлива ситуація, коли хтось запитує каталог
від Web-сервісу по HTTP, а хтось – по SMTP. Так як термін життя зберігається
не тільки в HTTP-заголовках, воно не загубиться при відправленні повідомлення по
SMTP.

Наступний клієнтський код на Microsoft ® Visual Basic ®. NET
ілюструє використання клієнтом властивості ValidUntil для того, щоб
визначити, чи треба оновити вміст кеша каталогу до виконання
запиту користувача.

 

Dim PencilResults() As org.pencilsellers.Pencil
If PencilCatalog.ValidUntil < Now() Then
    Dim Discovery As New org.pencilsellers.DiscoveryBinding()
    PencilCatalog = Discovery.GetCatalog()
End If
PencilResults = QueryCachedCatalog (PencilCatalog, QueryCriterion)

На серверній стороні ми повинні не тільки надати клієнтові відомості про
тому, коли закінчується термін зберігання даних (за допомогою елемента
ValidUntil), а й подумати про те, як уникнути створення каталогу "з
чистого аркуша "під час прийому кожного запиту. Один із способів досягнення
цього – додати параметр CacheDuration до атрибуту WebMethodAttribute. У
нашому випадку CacheDuration має один недолік: час зберігання даних
фіксується на етапі розробки. Встановивши CacheDuration рівним 24
години, ми можемо зіткнутися з такою проблемою.

Припустимо, ми створюємо каталог в 6 ранку 1-го квітня і встановлюємо
елемент ValidUntil, відповідним 6 ранку 2-го квітня. Ці дані
потраплять у перший же відповідь, і він опиниться в кеші вихідних даних ASP.NET.
Тепер припустимо, що приблизно о 10 вечора 1-го квітня надходить
величезна кількість запитів до інших ASP.NET-сторінок. Так як до
каталогу запити не надходять, система, ймовірно, видалить його з кеша,
щоб звільнити ресурси кеша для більш важливих даних. Потім о 10:30
вечора 1-го квітня надходить ще один запит до каталогу олівців. Так
як в кеші вихідних даних відповіді немає, Web-метод запускається ще раз,
при цьому термін життя даних встановлюється рівним 10:30 вечора 2-го
квітня. Тут-то проблема і виявляється: каталог олівців оновиться в 6
ранку 2-го квітня, а з кеша будуть як і раніше надходити дані за
вчорашній день. Так що нам потрібна система кешування, яка дозволяє
явно вказати термін зберігання даних у період виконання.

Кеш додатків ASP.NET пропонує простий спосіб вирішення цієї
проблеми. Запускаємо утиліту WSDL.EXE з. NET Framework SDK з параметром
командного рядка / Server і створюємо різні класи на базі
WSDL-визначення інтерфейсу Pencil Discovery, в тому числі клас Catalog,
заснований на певному в інтерфейсі типі. Ми просто створюємо каталог
з результатів SQL-запиту і додаємо його до кеша додатків ASP.NET
за допомогою методу Insert. Термін життя елемента в кеші встановлюється
рівним значенням ValidUntil каталогу. Код для Web-методу GetCatalog
наведено нижче.

Зауважте, що я все одно застосовую параметр CacheDuration для
додавання відповіді до кешу вихідних даних ASP.NET, але тепер термін життя
відносно малий – 10 хвилин. Тим самим я мінімізує час, протягом
якого можливе повернення застарілих даних, але все одно збільшую
продуктивність за рахунок кешування, що дуже корисно, коли до
каталогу надходить безліч запитів. Ми вважаємо, що більшість
запитів до каталогу надходить в межах 10 хвилин до закінчення терміну
зберігання даних.

<System.Web.Services.WebMethodAttribute( _
     CacheDuration:=600), _
 System.Web.Services.Protocols.SoapDocumentMethodAttribute( _
"Http://pencilsellers.org/2002/04/pencil/GetCatalog", _
     RequestNamespace:= _
"Http://pencilsellers.org/2002/04/pencil/discovery", _
     ResponseNamespace:= _
"Http://pencilsellers.org/2002/04/pencil/discovery", _
Use: = System.Web.Services.Description.SoapBindingUse.Literal, _
     ParameterStyle:= _
System.Web.Services.Protocols.SoapParameterStyle.Wrapped, _
     Binding:="DiscoveryBinding")> _
Public Overrides Function GetCatalog() As Catalog
    Dim PencilCatalog As Catalog
    If Context.Cache("PencilCatalog") Is Nothing Then
        PencilCatalog = CreateCatalog()
        Context.Cache.Insert("PencilCatalog", _
            PencilCatalog, _
            Nothing, _
            PencilCatalog.ValidUntil, _
            System.Web.Caching.Cache.NoSlidingExpiration)
    Else
PencilCatalog = Context.Cache ("PencilCatalog")
        If PencilCatalog.ValidUntil < Now() Then
            Context.Cache.Remove("PencilCatalog")
            PencilCatalog = CreateCatalog()
            Context.Cache.Insert("PencilCatalog", _
                PencilCatalog, _
                Nothing, _
                PencilCatalog.ValidUntil, _
                System.Web.Caching.Cache.NoSlidingExpiration)
        End If
    End If
    Return PencilCatalog
End Function

Висновок

Досить імовірно, що при розробці Web-сервісів XML вам захочеться
реалізувати який-небудь механізм кешування даних. Це можна зробити
різними способами: скористатися обмеженими функціями кешування
HTTP, виконувати кешування прикладного рівня на сервері, кешувати
відповіді на клієнті або просто розробити Web-сервіс так, щоб
"Інтелектуальні" клієнти знімали частину навантаження з сервера. У наступній
статті Скотт збирається розглянути проблему, з якою часто
стикаються розробники і споживачі Web-сервісів: злиття XML-даних
(XML merging). Скотт об'єднає каталог, що повертається Web-методом
GetCatalog, з каталогом іншій компанії, і Web-сайт зможе надавати
своїм користувачам загальний каталог олівців.

Мет Пауелл (Matt Powell) – член групи MSDN Architectural Samples.
Брав участь у розробці проривного SOAP Toolkit 1.0. Серед інших
досягнень Мета – книга "Running Microsoft Internet Information Server"
(Microsoft Press), написана у співавторстві, а також численні
статті в журналах. А вдома його завжди чекає чудова сім'я.

Схожі статті:


Сподобалася стаття? Ви можете залишити відгук або підписатися на RSS , щоб автоматично отримувати інформацію про нові статтях.

Коментарів поки що немає.

Ваш отзыв

Поділ на параграфи відбувається автоматично, адреса електронної пошти ніколи не буде опублікований, допустимий HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

*