Хорошо бы собраться вместе и согласовать, как именно все нужно сделать. Но тратить время на то, чтобы убедиться, что люди следуют руководящим установкам, менее привлекательно, чем выдвигать разработчикам требования реализовать все стандарты, следование которым ожидается от каждого сервиса. Но я твердо верю в то, что именно такой подход упрощает создание нужных вещей. Насколько я заметил, хорошо срабатывает использование экземпляров и предоставление шаблонов сервисов.
Написание документации — дело полезное. Я прекрасно это осознаю, ведь в конце концов я же написал эту книгу. И разработчикам нравится код, ведь они могут его запустить и исследовать. Если у вас есть предпочтительные стандарты или передовой опыт, то будет полезно иметь демонстрационные экземпляры. Замысел заключается в том, что, подражая некоторым наиболее удачным частям вашей системы, люди не смогут слишком сильно свернуть с верного пути.
В идеале для обеспечения правильного восприятия это должны быть реальные, имеющиеся у вас в наличии сервисы, а не изолированные сервисы, реализованные просто в качестве совершенных примеров. Уверяя в активном использовании своих экземпляров, вы гарантируете, что используемые вами принципы имеют смысл.
Как по-вашему, хорошо было бы иметь возможность действительно упростить разработчикам задачу следования большинству ваших указаний, затратив совсем немного усилий? Неужели не было бы замечательно предоставить в распоряжение разработчиков основную часть кода для реализации ключевых свойств, столь необходимых каждому сервису?
Есть два микроконтейнера с открытым кодом на основе JVM-машины — Dropwizard и Karyon. Они делают схожую работу, составляя набор библиотек для обеспечения таких свойств, как проверка работоспособности, обслуживание HTTP-протокола или экспортирование метрик. То есть вы получаете готовый сервис, укомплектованный встроенным сервлет-контейнером, который можно запустить из командной строки. Великолепный способ, чтобы пуститься в путь, почему бы им не воспользоваться? Раз уж все это имеется, почему бы не взять что-нибудь вроде Dropwizard или Karyon и не добавить дополнительные свойства, чтобы добиться соответствия вашему контексту?
Например, вам может потребоваться предписать использование предохранителей. В таком случае можно будет интегрировать такую библиотеку предохранителей, как Hystrix. Или у вас может практиковаться обязательная отправка всех метрик на центральный Graphite-сервер, поэтому нужно будет, наверное, ввести в действие такую библиотеку с открытым кодом, как Metrics из состава Dropwizard, и настроить ее таким образом, чтобы ее средствами показатели времени отклика и частоты появления ошибок автоматически помещались в известное место.
Приспособив подобный шаблон сервиса под собственный набор инструкций разработчикам, вы тем самым гарантируете то, что ваши команды смогут быстрее продвигаться, а также то, что разработчикам будет весьма трудно задать сервисам неверное поведение, не сбившись с заданного пути.
Разумеется, если используются несколько разнородных технологических стеков, то для каждого понадобится соответствующий шаблон сервиса. Правда, это может стать искусным способом ограничения выбора языка в вашей команде. Если имеющийся шаблон сервиса поддерживает только Java, то на выбор альтернативных стеков у людей может не хватить духу, если при этом им придется самостоятельно проделать существенно больший объем работы. К примеру, в Netflix особое внимание уделяется таким аспектам, как отказоустойчивость, позволяющая гарантировать невозможность сбоя всей системы при выходе из строя какой-либо ее части. Чтобы справиться с такой задачей, нужно проделать большой объем работы и убедиться, что существуют клиентские библиотеки на JVM, предоставляющие командам инструментарий, необходимый для удержания их сервисов в рамках дозволенного. Любое внедрение нового технологического стека будет означать необходимость повторно приложить те же усилия. О подобных повторных усилиях в Netflix заботятся меньше, считая, что порой пойти по неправильному пути очень просто. Если только что реализованный сервис может оказать весьма серьезное влияние на систему, то ошибки, связанные с его отказоустойчивостью, могут создать слишком высокий риск. Netflix смягчает проблему за счет применения попутных (sidecar) сервисов, ведущих локальный обмен данными с JVM-машиной, использующей соответствующие библиотеки.
Нужно остерегаться того, что создание шаблонов сервисов станет работой команды, разрабатывающей основной инструментарий или архитектуру, которая диктует, как все должно быть сделано, хотя и через код. При выработке инструкций нужно добиваться коллективной активности, поэтому в идеале ваша команда (или команды) должна нести общую ответственность за обновление такого шаблона (здесь хорошо срабатывает семейственный подход с открытым исходным кодом).
Мне известно множество случаев, когда принудительно навязываемые командам рамки портили людям настроение и снижали производительность. В стремлении повысить степень повторного использования кода все больше работы заключают в задаваемые из центра рамки, вплоть до того, что они превращаются в некое непреодолимое уродство. Принимая решение о применении подстраиваемых шаблонов сервисов, нужно очень тщательно продумать характер работы этих шаблонов. В идеале их использование должно носить чисто рекомендательный характер, но, если вы собираетесь настоять на их применении, следует понимать, что главным стимулом для разработчиков должна быть легкость этого процесса.
Также нужно осознавать опасность, которую может нести совместно используемый код. В стремлении создать многократно используемый код можно внедрить источники связывания сервисов друг с другом. По крайней мере, в одной из организаций я узнал, что их настолько беспокоила данная проблема, что они фактически вручную копировали код шаблона своего сервиса в каждый сервис. Следовательно, на обновление основного сервисного шаблона уходило больше времени, поскольку его нужно было применять в этой системе повсеместно, но их это волновало меньше опасности связывания. Другие команды, с которыми мне приходилось общаться, считали шаблон сервиса общей зависимостью двоичного кода, хотя при этом им нужно было очень постараться, чтобы не скатиться к тенденции DRY (don’t repeat yourself — «не повторяйся»), приводящей к чрезмерной связанности системы! С особенностями этой темы нужно будет разобраться, поэтому более подробно она рассматривается в главе 4.
Зачастую мы попадаем в такие ситуации, когда не можем следовать нашему техническому видению буквально, и, чтобы получить крайне необходимые функции, нам приходится срезать ряд углов. Это просто один из компромиссов, на которые надо идти. У нашего технического видения есть вполне веские основания. При отклонении от этих оснований могут быть получены краткосрочные преимущества, но в долгосрочной перспективе это может обойтись весьма дорого. Понятие, помогающее нам разобраться с этим компромиссом, называется техническим обязательством. Когда мы получаем техническое обязательство, похожее на обязательство в обычном мире, у него есть текущие затраты и нечто, что нам нужно выплатить по обязательству.
Порой для выполнения технического обязательства не находится кратчайших путей. Что, если наш взгляд на систему изменится, но не все в этой системе будет ему соответствовать? В такой ситуации также придется создавать новые источники технических обязательств.
Архитектор должен иметь более широкий кругозор и понимать суть такого баланса. Важно иметь представление об уровне обязательства и о том, куда нужно вмешиваться. В зависимости от вашей организации у вас могут быть возможности проводить мягкую линию руководства, добиваясь того, чтобы команды сами решали, как отслеживать ход работ и выполнять обязательства. В других же организациях может понадобиться четкая линия поведения, возможно ведение регулярно пересматриваемого журнала учета выполнения обязательств.
Таковы наши принципы и инструкции, определяющие порядок создания систем. А что происходит, когда система от них отклоняется? Иногда принимается решение о том, что это всего лишь исключение из правила. В таком случае стоит, наверное, зарегистрировать такое решение где-нибудь в рабочем журнале как напоминание на будущее. При возникновении довольно большого количества исключений может появиться смысл изменить принцип или инструкцию, чтобы в них отразилось новое понимание действительности. Например, у нас может существовать инструкция, утверждающая, что для хранения данных нужно всегда использовать MySQL. Но затем возникнет вполне резонный интерес к использованию в качестве широко масштабируемого хранилища системы Cassandra, и тут мы внесем в свою инструкцию поправку, гласящую: «Большинство требований по хранению данных удовлетворять за счет применения MySQL, пока не обнаружится перспектива большого разрастания их объемов, в случает чего применять Cassandra».
Наверное, еще раз нужно напомнить, что все организации разные. Мне приходилось работать с компаниями, где команды разработчиков пользовались высокой степенью доверия и автономности и инструкции не были строгими, а потребности в конкретной работе с исключениями значительно снижены или вовсе отсутствуют. В более жестко структурированных организациях, где разработчики имеют меньше свободы, отслеживание исключений может быть жизненно необходимым для того, чтобы убедиться: установленные правила четко отражают те сложности, с которыми приходится сталкиваться. После всего сказанного я сторонник микросервисов как способа оптимизации автономии команд, дающего им как можно больше свободы в решении текущих проблем. Если вы работаете в организации, накладывающей массу ограничений на деятельность разработчиков, то микросервисы могут вам и не подойти.