Thursday, December 29, 2011

Аутсорсинг и продуктовая разработка

Очень часто приходится слышать как программисты сравнивают и противопоставляют продуктовую и аутсорсинговую (outsourcing) разработку. И часто при этом говорят, что аутсорсинг - это ерунда и неинтересные проекты, а вот если разрабатывать продукты или начать стартап, то сразу будет счастье. Если быть до конца точными, то все это разделение не совсем корректно, т.к. сравниваются метры с килограммами.

Кстати, само слово “стартап” также часто используется неверно среди IT-шников. Стартап - это любой начинающийся бизнес, не только IT, просто наибольшее распространение он получил именно в нашей сфере в эпоху доткомов.

Сразу хочу извиниться перед теми, для кого эта информация будет сродни откровениям кэпа, но очень хочется внести ясность в этот вопрос.

Итак, компании, так или иначе занимающиеся разработкой ПО, можно условно разделить на 2 группы:

  1. производящие ПО для собственных нужд или с целью самостоятельного распространения (продукты и услуги)
  2. предоставляющие услуги по разработке ПО для сторонних заказчиков

Вторую группу составляют именно те компании, которые обычно называют аутсорсинговыми, но так ли это, что они не разрабатывают продукты? В первую группу входят компании, которые обычно называют продуктовыми, но единственные ли они в этой группе?

С точки зрения рынка разработки ПО, это самое ПО, в свою очередь, также можно разделить на 2 группы:

  1. продукты (в основном настольные, мобильные и embedded, реже - веб), а также веб-решения, предоставляющие пользователям различные услуги, но предназначенные для использования большим количеством сторонних пользователей, а не самой компанией
  2. custom solutions - индивидуальные решения, предназначенные для внутреннего использования одним потребителем, например, внутри компании или организации

Если и другие классификации ПО, но нам они сейчас не очень интересны.

Понятно, что custom solutions могут разрабатываться как внутри самой компании (in-house), так и на заказ у какого-нибудь вендора. Но ведь и продукты со “стартапами” не всегда разрабатываются внутри компании, которая потом эти продукты продает.

Если скрестить это все, то получим следующую картинку:

Custom_vs_products

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

Поймите меня правильно, я ни в коем случае не хочу сказать, что разработка ПО на заказ (по сути, продажа труда, знаний и времени разработчиков) - лучше, чем разработка и продажа собственных продуктов или услуг на базе собственных решений. Разрабатывать продукты сложнее, потому что лишь 20-30% этого процесса - непосредственно разработка, остальное - анализ рынка и конкурентов, работа с пользователями, и самое главное - продажи. И после всех усилий и вложенных средств еще неизвестно - взлетит или не взлетит. Продавать услуги по разработке ПО в этом отношении намного проще и гораздо менее рискованно. В то же время и возможности заработка в аутсорсинговой модели ограничены рейтами, а разница между доходами и расходами невелика, в то время как в случае успеха продукта или стартапа доход может быть в разы, десятки и сотни раз выше, чем расходы. Но, как известно, кто не рискует, тот не пьет шампанского.

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

Monday, December 26, 2011

Распределенные транзакции (Distributed Transactions) и их настройка

Каждый программист, работающий с данными, сталкивался с обычными транзакциями той или иной базы данных. Основная задача транзакций – обеспечить consistency данных после завершения операции: изменения либо успешно сохраняются от начала и до конца, либо полностью откатываются, если что-то пошло не так. Даже если вы ни разу не писали в SQL коде ключевые слова BEGIN TRAN, COMMIT TRAN или ROLLBACK TRAN или нечто подобное, это еще не значит, что вы их не использовали. Все ORM, реализующие паттерн unit of work (Entity Framework, NHibernate и др.) объединяют операции по изменению данных в транзакцию перед сохранением.

Транзакции хорошо работают в рамках одной базы данных, но если у вас распределенная система, которая требует сохранения данных в разных базах на разных серверах, а иногда и платформах, то нужна более тяжелая артиллерия – распределенные транзакции (distributed transactions, DT).

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

Truly Understanding .NET Transactions and WCF Implementation

Работа вложенных транзакций отлично расписана здесь:

Understanding nested transaction scopes

Также не забывайте про MSDN:

http://msdn.microsoft.com/en-us/library/w97s6fw4(v=VS.90).aspx

Итак, .NET поддерживает распределенные транзакции при помощи класса TransactionScope. Кроме этого, основная технология разработки распределенных приложений, WCF, также поддерживает распределенные транзакции из коробки, предоставляя программисту целый комплекс конфигурационных параметров и атрибутов, которые позволяют с легкостью превратить ваш сервис из обычного в распределенно-транзакционный.

Для того, чтобы запустить ваш сервис в режиме поддержки распределенных транзакций, необходимо выполнить несколько шагов, щедро описанных в книгах, а также статьях и блогах в Интернете:

1) Сконфигурировать WCF для поддержки DT

Распределенные транзакции не поддерживаются в режиме basicHttpBinding, поэтому нам нужно использовать хотя бы на wsHttpBinding, в binding которого нужно прописать атрибут transactionFlow=”true”:

<wshttpbinding>
    <binding name="wsConfig" transactionflow="true">
         <security mode="None" />
    </binding>
</wshttpbinding>

2) Установить специальные атрибуты в интерфейсе сервиса и его методов

Необходимо добавить атрибуты TransactionFlow для метода в контракте и свойства атрибута OperationBehavior TransactionScopeRequired и опционально TransactionAutoComplete в реализации метода:

[ServiceContract] 
public interface IServiceContract 
{ 
    [OperationContract] 
    [TransactionFlow(TransactionFlowOption.Allowed)] 
    string ServiceMethod(string param1, string param2); 
}

public class Service : IServiceContract 
{ 
    [OperationBehavior(TransactionScopeRequired = true, TransactionAutoComplete = true)] 
    public string ServiceMethod(string param1, string param2) 
    { 
      // some server-side operations with database 
    } 
}

Атрибут TransactionFlow принимает несколько опций: Allowed обозначает, что метод сервиса может вызываться как из кода, обернутого в TransactionScope, так и из обычного. Mandatory требует наличия TransactionScope, а NotAllowed (по умолчанию) заставит сервис игнорировать транзакции на клиенте вообще.

3) Создать на стороне клиента транзакцию, внутри которой вызвать метод WCF сервиса

Выглядит это приблизительно так:

public static string DoSomethingWithRemoteCall(string param1, string param2) 
{ 
    string result = null; 

    TransactionOptions options = 
        new TransactionOptions { IsolationLevel = IsolationLevel.ReadCommitted, Timeout = TimeSpan.FromSeconds(300) }; 
    using (TransactionScope scope = new TransactionScope(TransactionScopeOption.RequiresNew, options)) 
    { 
        // some client-side database actions 

        using (var ecommerceFactory = new ChannelFactory("Staging")) 
        { 
            ecommerceFactory.Open(); 

            var proxy = ecommerceFactory.CreateChannel(); 
            bool success = false; 

            try 
            { 
                result = proxy.ServiceMethod(param1, param2); 
                success = true; 
            } 
            catch (Exception ex) 
            { 
                // log exception 
                return;

            } 
            finally 
            { 
                if (success) 
                    ecommerceFactory.Close(); 
                else 
                    ecommerceFactory.Abort(); 
            } 
        } 

        // other possible client-side database actions
        scope.Complete(); 
    } 

    return result; 
}

Безусловно, в вашем коде вызов сервиса должен находиться в отдельном классе, это лишь пример.

TransactionOptions позволяет задать некоторые параметры транзакции. В нашем случае это уровень изоляции (значение по умолчанию Serializable не рекомендуется из-за опасности возникновения дедлоков) и таймаут операции (5 минут).

При создании TransactionScope мы указываем параметр TransactionScopeOption.RequiresNew, что не позволит никакому другому коду обернуть наш метод в свою транзакцию. Подробнее вложенные транзакции описаны здесь.

Внутри TransactionScope в случае, если мы действительно хотим закоммитить транзакцию, мы делаем вызов scope.Complete(). Если нам нужно транзакцию откатить (как в случае с catch в примере), мы просто не вызываем Complete(). Вызывать Complete() нужно после всех клиентских операций с базой данных, которые происходят внутри транзакции, иначе у вас случится ошибка, что connection или provider уже закрыт.

Обратите внимание, что в коде сервиса из предыдущего пункта нет никакого намека на TransactionScope, кроме атрибутов TransactionFlow и OperationBehavior. Он там и не нужен, для стандартного сценария атрибутов достаточно. Однако никто вам не мешает создавать свои вложенные транзакции, как с опцией Required (используем родительскую транзакцию), так и с опциями RequiresNew (новая независимая транзакция) и Suppress (код не будет выполнятся в родительской транзакции).

4) Запустить сервис и клиент

И вуаля – все работает. Или не работает? Говорите, полезли странные ошибки выполнения?

Для того, чтобы распределенные транзакции заработали, необходимо сделать еще правильно сконфигурировать клиенты и сервера:

1) Убедиться, что на всех клиентах и серверах (здесь и далее - включая сервера баз данных и веб-сервера) установлена и запущена служба Distribution Transactions Coordinator. Именно эта служба отвечает за координацию ваших распределенных транзакций.

2) Убедиться, что на всех клиентах и серверах включена поддержка распределенных транзакций. Для этого запускаем Control Panel –> Administrative Tools –> Component Services, идем в Computers –> My Computer –> Distributed Transaction Coordinator –> Properties (контекстное меню) и устанавливаем на вкладке Security следующие параметры:

  • Network DTC Access
  • Allow Remote Clients
  • Allow Inbound
  • Allow Outbound

Component_Services

3) Разрешить работу Distributed Transactions Coordinator во всех установленных брандмауэрах, включая Windows Firewall:

Firewall

4) Убедиться, что все ваши клиенты и сервера находятся в одной локальной сети. В большинстве случаев так оно и есть, но есть исключения, и если вы тот самый счастливчик, то вам придется немного попотеть, реализовывая поддержку протокола WS-Atomic Transaction (WS-AT), который упоминается в общей статье. Если вы тот самый счастливчик, которому надо начинать настраивать WS-AT, то вот еще пара полезных статей:

Configuring WS-Atomic Transaction support (MSDN)

Building transactional Web services with WebSphere Application Server and Microsoft .NET using WS-AtomicTransaction

5) Для работы DTC по локальной сети: все машины должны пинговаться по netbios-имени.

6) Важно: Если вы пошли по нашему пути и запустили тестовую конфигурацию на виртуальных машинах, запущенных с одного образа: переустановить DTC. DTC не работает с одинаковыми CID, а переустановка их сбрасывает. Это проблема, с которой мы столкнулись и которую смогли найти лишь запустив утилиту DTCPing.

Больше деталей здесь: Warning: the CID values for both test machines are the same

7) Если ничего не помогло: поставить и запустить DTCPing и посмотреть, что она говорит. Очень хороший способ, когда ничего другое не помогает:

Troubleshooting MSDTC issues with the DTCPing tool

Saturday, December 3, 2011

Paypal: работа с 3rd party merchant accounts

Интеграция с Paypal – вещь не сложная. Paypal поддерживает два типа API: Name-Value pairs (URL-based) и стандартный SOAP, а также несколько сервисов для работы, из которых самые распространенные Website Payments Standard и Website Payments Pro (Express Checkout и Direct Payments). Paypal также предоставлят Sandbox для тестирования и достаточно подробную документацию по каждому продукту и типу интеграции. Это если вкратце.

На деле же, как и с любым другим API, здесь есть свои подводные камни, с которыми приходится бодаться. Об одном из них, на который я потратил довольно много времени, мне и хотелось бы рассказать.

Для того, чтобы ecommerce сайт работал с Paypal, необходимо иметь Paypal merchant аккаунт, на который идет вся оплата за товары и услуги, продаваемые на сайте. Для авторизации на Paypal для аккаунта генерируются т.н. API Credentials: Username, Password и Signature/Certificate (лучше использовать Signature). Как это делается, в деталях рассказывается в документации. То есть механизм простой: веб-приложение передает запросы на Paypal, используя три параметра API Credentials, а Paypal таким образом “знает”, с каким merchant аккаунтом идет работа, грубо говоря, кому перечислять деньги за товары.

Все это замечательно работает, когда у вас в приложении “хостится” один продавец, и так работает наверно 95% всех ecommerce-сайтов, от простейших шаблонных электронных магазинов до более серьезных решений. Но в случае реализации сложной торговой площадки, на которой может работать множество торговцев, вроде Amazon.com, eBay.com или etsy.com, этого становится недостаточно. Есть два способа решения этой задачи.

Первый способ очевидный: пусть каждый merchant сгенерирует себе API Credentials и укажет их в своем профиле торговой площадки. Тогда при работе с выбранным Paypal API нужно будет всего лишь подставлять нужные credentials – и вуаля. Однако если посмотреть на профили того же Amazon или eBay для торговцев, то можно увидеть, что они просят ввести только Merchant account email. И никаких паролей или API credentials! Это и есть второй способ интеграции, не так хорошо задокументированный.

Упоминание об этом способе почему-то достаточно хорошо скрывается в документации к функциям API и фактически встречается лишь в описании работы с Name-Value pairs API и на форумах (кстати, форумы Paypal очень полезны, но гуглом не индексируются, поэтому лучше искать прямо там). Вот оно:

image

То есть можно сгенерировать API Credentials для всего приложения и динамически подменять лишь SUBJECT поле для каждого запроса, таким образом говоря Paypal, с каким merchant сейчас идет работа. И все бы хорошо, да только NOTE там указан не просто так. Paypal API достаточно мощный и дает возможность обращаться к некоторой внутренней информации merchant аккаунта, поэтому необходимо, чтобы merchant дал разрешение на определенные операции. Эти разрешения merchant может задать в Profile –> API Access –> Grant API Permissions. Далее нужно ввести API account username торговой площадки, которой даются разрешения и в появившемся списке выбрать те разрешения, которые нужны. Например, для работы с Express Checkout нужно выбрать как минимум:

  • Use Express Checkout to process payments

Также полезными могут оказаться:

  • Authorize and capture your PayPal transactions – если оплата проходит в 2 этапа: авторизация и захват/списание денег
  • Refund a transaction on your behalf – для возврата денег в случае возврата товара
  • и некоторые другие (полный список есть здесь)

Дальше – больше. Процесс разрешения прав можно упростить для торговцев еще сильнее, если использовать т.н. Paypal Permissions Service. Этот сервис предоставляет возможность интеграции торговой площадки со специальными страницами разрешения прав на Paypal. То есть пользователю-торговцу достаточно лишь кликнуть ссылку или кнопку на сайте торговой площадки, и он будет автоматически переброшен на страницу Paypal с уже выбранным набором прав для разрешения, необходимых вашему приложению. Пользователю останется лишь подтвердить разрешение.

Тестирование этого процесса также заслуживает внимания, так как там притаилась еще одна небольшая засада. Вам необходимо создать 2 (!) различных sandbox-аккаунта, один из которых будет использоваться для создания API Credentials веб-приложения, а второй – для создания тестовых merchant-аккаунтов, которые будут якобы продавать товары. И неважно, что merchant (или seller) аккаунты можно создать и на основном sandbox аккаунте, вам не удастся сконфигурировать нужные права и Paypal будет возвращать непонятные ошибки.

Надеюсь, для кого-то эта информация окажется полезной и сэкономит время.