Дефрагментация мозга. Софтостроение изнутри
Шрифт:
В начале 2000-х в ИТ-отрасли разразился кризис лопнувшего пузыря интернет-компаний, продвигать сложные и дорогие инфраструктуры, а коммерческие реализации CORBA стоили порядка тысячи долларов за рабочее место, стало очень трудно. Постепенно из кустов начали выкатывать на сцену рояль веб-сервисов, который из-за проблем с CORBA должен был стать новой платформой интеграции служб и приложений в гетерогенной среде. Концепции быстро придумали многообещающее название СОА. Надо сказать, что рояль не стоял без дела и в кустах: разработка веб-служб и развитие XML велись в конце 1990-х и активно продолжились в 2000-х годах. Поскольку это была технология, хотя и весьма базового уровня, но относительно простая, дешёвая и открытая не только на уровне спецификаций, но и в виде множества
Говоря о базовом уровне технологии, я имею в виду не столько отсутствие сравнимой с CORBA функциональности и номенклатуры служб и средств [80] , сколько необходимость программистам самим надстраивать над этим базисом многое из того, что было даже в самом минимальном варианте CORBA.
Веб, точнее, его основа, HTTP – среда без состояния и пользовательских сессий. В общедоступном Интернете такое решение было вызвано соображениями нагрузки, поскольку максимальное число запросов к серверу теоретически равно количеству всех устройств в сети. В корпоративной же системе нагрузку на службу можно (и нужно) рассчитать гораздо точнее. В итоге программистам, не связанным с веб-разработкой для Интернета, приходится восполнять недостаток средств протокола надстройками поверх него костылей, например, постоянно гоняя контекст и состояние в сообщениях или симулируя сессии по тайм-ауту.
В CORBA сессии поддерживались средой без дополнительных усилий. Если в частных случаях возникали вопросы нагрузки на поддержку соединений при большом количестве клиентов, они решались так же просто, как и в среде СУБД: приложение самостоятельно отсоединялось от сервера, выполнив пакет необходимых запросов. При желании нетрудно было также организовать и принудительное отсоединение по истечении заданного периода пассивности. Но для корпоративной службы, напомню, речь идёт обычно о десятках и сотнях активных сессий, поддержка которых в большинстве случаев укладывается в ресурсы серверов.
Неприятным следствием отсутствия сессий стала невозможность поддержки транзакций при работе с веб-службами. Для решения проблемы в рамках ООП необходим шаблон «Единица работы» ( unit of work ), суть которого в передаче веб-службе сразу всего упорядоченного множества объектов, подлежащих сохранению в управляемой сервером транзакции.
В общем случае шаблон является аналогом пакетной обработки транзакций, необходимой для сокращения времени жизни единичной транзакции. Например, в репликации данных между СУБД сиквел-операции передаются пачками. Но если раньше такой подход был разновидностью оптимизации и средством избавления от толстых транзакций, то теперь его необходимо было использовать всегда, вместо любой транзакции вообще.
Давайте сравним близкий к реальному псевдокод на сторонах клиента в рамках CORBA с псевдокодом в среде веб-служб. При поддержке сессии все достаточно прозрачно и не нуждается в комментариях.
Псевдокод транзакции в среде CORBA
CosTransactions.Current current = CosTransactions.CurrentHelper.Narrow(
orb.ResolveInitialReferences("TransactionCurrent"));
current.Begin;
try
{
store1.Remove(product, quantity);
store2.Append(product, quantity);
current.Commit;
}
catch (Exception e)
{
current.Rollback;
ShowError("Ошибка выполнения операции: " + e.toString);
}В среде веб-служб в программе-клиенте приходится надстраивать абстракции DTO [81] . А в серверном приложении, где используются соединения, например, с СУБД или монитором транзакций, необходимо фактически дублировать предыдущий код с раскруткой объекта – единицы работы (unit of work) в реальную транзакцию.
Псевдокод транзакции в среде веб-служб
StoreServiceClient storeServiceClient = new StoreServiceClient(url);
StoreOperationDTO operation1 = storeServiceClient.CreateOperation(store1.Id);
operation1.Type = StoreOperations.Remove;
operation1.ProductId = product.Id;
operation1.Quantity = quantity;
StoreOperationDTO operation2 = storeServiceClient.CreateOperation(store2.Id);
operation2.Type = StoreOperations.Append;
operation2.ProductId = product.Id;
operation2.Quantity = quantity;UnitOfWork uow = new UnitOfWork;
uof.RegisterDirty(operation1);
uof.RegisterDirty(operation2);
try
{
storeServiceClient.ProcessOperations(uow);
}
catch (Exception e)
{
ShowError("Ошибка
выполнения операции: " + e.toString);}Вторым «упрощением» стал переход от понятных прикладному программисту деклараций интерфейсов объектов и служб на языке IDL [82] к WSDL [83] – описаниям, ориентированным, прежде всего, на обработку компьютером. Сравним декларации складской службы, возвращающей по запросу текущее количество товарных позиций.
Декларация службы в CORBA IDL
module StockServices
{
typedef float CurrentQuantity;
struct QuantityRequest
{
string stockSymbol;
};
interface StockInventoryService
{
CurrentQuantity getCurrent(in QuantityRequest request);
};
};Декларация службы в WSDL
<?xml version ="1.0" encoding ="utf-8"?>
<definitions name ="StockInventoryService"
xmlns: sqs ="http://mycompany.com/stockinventoryservice.wsdl"
xmlns: sqsxsd ="http://mycompany.com/stockinventoryservice.xsd"
xmlns: soap ="http://schemas.xmlsoap.org/wsdl/soap/"
xmlns: wsdl ="http://schemas.xmlsoap.org/wsdl/"
xmlns: xsd ="http://www.w3.org/2000/10/XMLSchema">
<wsdl: types>
<xsd: element name ="CurrentQuantity">
<xsd: complexType>
<xsd: all>
<xsd: element name ="stockSymbol" type ="string"/>
</xsd: all>
</xsd: complexType>
</xsd: element>
<xsd: element name ="CurrentQuantity">
<xsd: complexType>
<xsd: all>
<xsd: element name ="quantity" type ="float"/>
</xsd: all>
</xsd: complexType>
</xsd: element>
</wsdl: types><xsd: element name ="CurrentQuantity">
<xsd: complexType>
<xsd: all>
<xsd: element name ="quantity" type ="float"/>
</xsd: all>
</xsd: complexType>
</xsd: element>
</wsdl: types>
<wsdl: message name ="getCurrentInput">
<wsdl: part name ="body" element ="sqsxsd: CurrentQuantity"/>
</wsdl: message><wsdl: message name ="getCurrentOutput">
<wsdl: part name ="body" element ="sqsxsd: CurrentQuantity"/>
</wsdl: message>
<wsdl: portType name ="StockInventoryServicePortType">
<wsdl: operation name ="getCurrent">
<wsdl: input message ="sqs: getCurrentInput"/>
<wsdl: output message ="sqs: getCurrentOutput"/>
</wsdl: operation>
</wsdl: portType><wsdl: binding name ="StockInventoryServiceSoapBinding"
type ="sqs: StockInventoryServicePortType">
<soap: binding style ="document" transport ="http://schemas.xmlsoap.org/soap/http"/>
<wsdl: operation name ="getCurrent">
<soap: operation soapAction ="http://mycompany.com/getCurrent"/>
<wsdl: input>
<soap: body use ="literal"/>
</wsdl: input>
<wsdl: output>
<soap: body use ="literal"/>
</wsdl: output>
</wsdl: operation>
</wsdl: binding>