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


На одном из слайдов этого доклада из минусов микросервисной архитектуры указывалась сложность тестирования. В большинстве источников тестирование микросервисных приложений практически не упоминается, поэтому возникло желание по возможности разобраться в возможностях тесторавания микросервисной архитектуры (MSA), понять, что надо учитывать на этапе дизайна такого приложения, и как максимально облегчить жизнь себе и ближнему своему.


Микросервисы. Начало.


Покопавшись в интернете я нашел кучу информации о MSA (MicroService Architecture) и его старшем брате SOA (Service Oriented Architecture), в том числе и на Хабре, поэтому не буду останавливаться подробно на том, что это такое. Вот кратко основные принципы MSA:


  • Модульность. Приложение состоит из нескольких сервисов, представляющих собой законченные приложения
  • Независимость реализации. Сервисы могут быть реализованы на различных языках программирования, с использованием различных технологий

Отсюда ряд плюсов:


  • Высокая стабильность. При отказе одного из сервисов, например, из-за программной ошибки, есть возможность откатить неработающий сервис или установить новую, более стабильную версию, не перезапуская всего приложения.
  • Разнообразие технологий. Сервисы не ограничены только одной технологией, принятой для всего приложения.
  • Независимое развертывание. Простые сервисы проще разворачивать, и меньше вероятность отказа системы.

И недостатков:


  • Сложность разработки из-за наличия различных технологий.
  • Время работы напрямую зависит от того, как сервисы общаются между собой.

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


«А что нам надо?»


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


Если приложение относительно небольшое, то можно обойтись поддержкой REST-заросов, которые могут отсылаться непосредственно вовлеченными во взаимодействие сервисами. Это значительно упрощает архитектру приложения в целом, но приводит к существенным затратам на передачу информации (никогда не используйте синхронные запросы, если хотите чтобы ваше MSA-приложение работало достаточно быстро!). При достаточно сложном приложении без создания менеджера не обойтись.


Для увеличения стабильности работы даже простого приложения лучше реализовать менеджер сообщений. Такой менеджер должен принимать асинхронный запрос от одного сервиса и передавать его другому. Он может быть реализован на сокетах, веб-сокетах, или любой другой удобной технологии. Запросы лучше хранить в очередях. При таком подходе появляется простой инструмент мониторинга взаимодействия сервисов между собой, даже если сейчас, на первый взгляд, нам этого и не надо.


Создание менеджера сообщений подразумевает, что его интерфейс должен быть стандартным и поддерживаться всеми сервисами продукта. С другой стороны, использование различных интерфейсов обмена сообщений у различных сервисов приведет к излишнему усложнению кода. Единый интерфейс так же подразумевает, что его дизайн должен быть готов перед началом кодирования.


"Мы делили апельсин..."


Теперь давайте рассмотрим основополагающую идею MSA: взаимодействие относительно независимых сервисов между собой.


Из плюсов стоит отметить возможность подмены одного сервиса другим без необходимости переустанавливать все приложение. Из недостатков — сервисы должны быть а) достаточно маленькие, б) достаточно автономные.


Решением здесь может быть правильное разбиение кода по сервисам. Причем деление должно быть не как в макроприложении, по функционалу (UI, Network, Back-End computing, DB), а по бизнес логике, например: обработка запроса на вход в систему, составление отчета по продажам, построение графика по данным из базы. Такие функционально-законченные модули становятся действительно независимыми и их применение становится очевидным. Кроме того, общий функционал приложения может быть легко и безболезненно расширен либо изменен.


Как его тестировать?


Если с макроприложением в плане тестирования было все понятно, то что же делать здесь? Куча сервисов, каждый из них может "дергать" множество других, данные хаотично пересылаются между сервисами… Кошмар! Но так-ли это?


Если мы все сделали правильно, то у нас есть приложение, которое:


  • Состоит из набора функционально законченных сервисов
  • Взаимодействие между сервисами происходит через менеджер сообщений
  • Интерфейс сообщений стандартный у всех сервисов.

С точки зрения ручного тестирования, работа с каждым сервисом в отдельности является огромной головной болью. Зато какой простор для автоматизации!


Прежде всего давайте подключим логгер к нашему менеджеру сообщений, чтобы получить четкий и понятный лог работы каждого сервиса, при этом взаимодействие сервисов между собой так же становятся прозрачным. А значит мы можем быстро выявить проблемный сервис и при необходимости его откатить. В случае WEB-приложения можно реализовать мониторинг, который будет в режиме реального времени сообщать нам о возникших проблемах.


Т.к. интервейс сообщений у нас стандартный, нам не надо подстраиваться под каждый сервис в отдельности, достаточно использовать набор известных пар "запрос-ответ", например, из той же БД. А это так всеми любимый DDT (Data Driven Testing, не путать с рок-группой и/или пестицидом!), что приводит нас к потрясающей масштабируемости и производительности.


По условию задачи каждый сервис у нас — отдельная функционально-законченная единица. Совсем как функция или метод в макроприложении. Логично, если на каждый сервис будет написан набор "unit"-тестов. В кавычках, потому что мы тестируем не методы и функции, а сервисы с несколько более сложным функционалом. И опять-таки, нет совершенно никакой необходимости эмулировать действия пользователя, достаточно сформировать корректный REST-запрос. После реализации этого пункта можно сказать, что acceptance тесты разработаны для каждого сервиса. Более того, здесь опять напрашивается DDT — один тест применяется к разным сервисам, меняются только наборы входных/выходных данных.


Тестовый стенд


Таким образом у нас очень быстро набралось неимоверное количество тестов, которые надо где-то запускать. Естественно, что тестовый запуск на одном сервере будет занимать достаточно долгое время, что нас никак не устраивает.


Для WEB-приложений решение очевидно: можно разворачивать отдельный пре-конфигурированный сервер для каждого запуска. Это не уменьшит нагрузку на сервер, но позволит разделить тестируемые сервисы между собой. Если запуск проводится в контролируемой среде, где источником новых багов будет только тестируемый сервис, то можно существенно сократить набор запускаемых тестов. Это очень важно на стадии разработки — когда разработчик получает возможность проверить свой функционал во взаимодействии с другими сервисами, не особо отвлекаясь на запуск полного набора тестов на своей машине.


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


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


Выводы


MSA — очень интересная и гибкая архитектура как для разработки, так и для тестирования. При правильном балансе простоты и универсальности, четком понимании структуры приложения, можно получить хорошую приозводительность при минимальных трудозатратах.


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


Важно понимать, что для успешной и эффективной автоматизации тестирования MSA-приложений нужно четкое и плотное взаимодействие команд разработчиков и автоматизаторов между собой.


Что почитать:


Микросервисы (Microservices)
Преимущества и недостатки микросервисной архитектуры
Microservices. Как правильно делать и когда применять?

Поделиться с друзьями
-->

Комментарии (34)


  1. Aries_ua
    22.06.2016 16:31
    +1

    Скажите, на сколько REST будет просаживать производительность?


    1. Dreyk
      22.06.2016 18:21
      +2

      REST — это архитектурный стиль, он не может влиять на производительность. На производительность влияет конкретная реализация


    1. M-A-XG
      23.06.2016 08:55

      Под REST вы понимаете HTTP взаимодействие вообще?

      Просадка будет в зависимости от того, как работает сервер.
      Вот у меня страничка генерируется 4мс. Но nginx отдает ее за 55мс. Хз как уменьшить.

      Если за данными нужно стучаться далеко, просадка будет больше.


    1. VolCh
      23.06.2016 09:51
      +2

      Вопрос не имеющий ответа. На сколько по сравнению с чем? В общем случае незначительно по сравнению с другими архитектурными стилями типа RPC, если вообще не выигрывать будет, из-за того, что серверу не надо хранить состояние сессии — всё что нужно для обработки запроса ему клиент в запросе будет предоставлять.


      1. Aries_ua
        23.06.2016 22:37

        Цитата из текста:

        «Если приложение относительно небольшое, то можно обойтись поддержкой REST-заросов, которые могут отсылаться непосредственно вовлеченными во взаимодействие сервисами. Это значительно упрощает архитектру приложения в целом, но приводит к существенным затратам на передачу информации (никогда не используйте синхронные запросы, если хотите чтобы ваше MSA-приложение работало достаточно быстро!)».

        На сколько я понял автора, то имелось ввиду, что REST будет менее быстрым чем что-то другое.
        Поэтому я решил уточнить, что подразумевалось.


        1. VolCh
          24.06.2016 07:47

          Неудачная фраза, кажется. В общем и в целом нельзя сказать, что REST-запросы медленнее или быстрее других, а введение посредника в любом (кроме каких-то экзотических, связанных с конфигурацией сети) случае замедлит получение ответа. Но упростит архитектуру и ускорит разработку, как-только начнутся связи один-ко-многим.


  1. schroeder
    22.06.2016 16:54

    Можно поподробнее вот про это:

    никогда не используйте синхронные запросы, если хотите чтобы ваше MSA-приложение работало достаточно быстро!

    Что имеется ввиду? Не опрашивать все нужные сервисы по очереди, а паралельно? Или что то другое?


    1. oxidmod
      22.06.2016 17:09

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


      1. schroeder
        22.06.2016 18:04

        Спасибо. Но в таком случае агрегатор никогда не покажет эти самые коменты, т.к. они придут слишком поздно и клиент уже покажет страницу. Клиенту придется отправить запрос на коменты еще раз и в этот раз ему придется таки дождатся ответа. Это не то что понимают под ассинхронностью, вернее совсем не то что понимаю я.


        1. Dreyk
          22.06.2016 18:17

          тут несколько вариантов


          • грузить коменты асинхронно после загрузки страницы (так например youtube делает)
          • кешировать коменты в микросервисе блогов, и обновлять кеш по мере обновления коментов (тогда "запрос" от сервиса блогов к сервису коментов будет вне контекста реквест-респонс для юзера и тоже "асинхронен")

          а в первом случае асинхронность в том, что коменты грузятся в то время, пока пользователь уже читает бложек


          1. schroeder
            22.06.2016 20:55

            что это имеет общего с агрегатором, который должен ассинхронно работать с микросервисом?


        1. oxidmod
          22.06.2016 19:12

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


          1. schroeder
            22.06.2016 20:54

            ну так это уже с агрегатором ничего общего не имеет, а значит об асинхронности работы с микросервисом разговор не идет.


            1. oxidmod
              22.06.2016 21:41

              возможно я и не прав. подождем тогда ответа автора)
              зы. тут подумалось. возможно автор имел ввиду что-то типа менеджера очередей. И предлагает асинхронно делать то, что можно делать в фоне.


              1. VolCh
                23.06.2016 07:28

                Ждать ответов от микросервисов можно (а обычно и нужно) в фоне.


              1. antstar
                23.06.2016 09:10

                Основной смысл — не допускать простоя. Менеджер очередей нужен нам прежде всего для логирования. С точки зрения разработчика это возможно и лишний элемент, но при тестировании очень удобно отслеживать взаимодействие процессов именно через него.


                1. VolCh
                  23.06.2016 09:59

                  Единая шина общения между сервисами (в том числе организованная на базе менеджера очередей) нужна для унификации общения между сервисами. Логирование лишь частный случай. Сервисы как паблишеры отправляют в шину сообщения о важных (с их точки зрения) событиях, а другие сервисы как сабскрайберы предпринимают какие-то действия по получению важного (с их точки зрения) события, например запись в лог.


                  1. antstar
                    23.06.2016 10:08

                    Согласен, спасибо


              1. antstar
                23.06.2016 09:13

                Вы и сами неплохо справляетесь, спасибо за такой живой интерес к статье.


            1. VolCh
              23.06.2016 07:27

              Во-первых, сам агрегатор работает с микросервисами асинхронно, он не ждёт ответа от них. Во-вторых, и его клиенты могут работать с ним асинхронно по тем же веб-сокетам. Клиент даст запрос «дай мне пост с ид 303778 и комментарии к нему», агрегатор сначала отдаст пост, а потом комментарии (или наоборот, смотря что будет готово раньше).


        1. VolCh
          23.06.2016 07:23

          Под асинхронностью обычно понимают отсутствие необходимости ждать ответа. Клиент отправляет запрос и занимается другими делами, обрабатывая ответ только когда он будет (если он ему вообще нужен, что не факт). В примере с агрегатором, при поступлении запроса от клиента по синхронному протоколу (например http запрос от браузера), он отправит два запроса к сервисам практически одновременно и отдаст управление ядру без ожидания ответа. Когда первый из ответов (не факто что это ответ на ушедший первым запрос) будет готов, ядро вызовет агрегатор уведомлением о готовности, агрегатор скопирует ответ в свой буфер (или сохранит указатель на буфер полученный от ядра) и опять отдаст управлению ядру. И только когда второй ответ будет готов, агрегатор полностью сформирует и отдаст ответ по синхронному протоколу. Агрегатор работает асинхронно, он не ждёт ответа от сервисов ни такта процессора, его работа по другим запросам не блокируется ожиданием.

          А что понимаете под асинхронностью вы?


          1. oxidmod
            23.06.2016 08:50

            это все прикольно, но как быть с таким языком как php, например. Асинхронности из коробки у него нет. Но микросервисы на нем пилят)


            1. antstar
              23.06.2016 09:58

              Синхронности у него, собственно, тоже нет. Вы, уважаемый, путаете дизайн и API.


              1. oxidmod
                23.06.2016 10:24

                объясните тогда пожалуйста, что вы имели ввиду под фразой
                «никогда не используйте синхронные запросы, если хотите чтобы ваше MSA-приложение работало достаточно быстро!»

                в контексте языка без async/await


                1. VolCh
                  23.06.2016 10:29

                  Есть языки с поддержкой асинхронных операций на уровне синтаксиса, а есть языки, в которой асинхронность достигается внешними средствами, например ядром ОС.


            1. VolCh
              23.06.2016 10:25

              Как это нет асинхронности в PHP? Как минимум с PHP 4 socket_select() есть, а больше ничего особо и не нужно для построения асинхронных сервисов.


          1. schroeder
            23.06.2016 10:28
            +1

            Спасибо за ответ, но вы сказали абсолютно тоже самое, что сказал я: ваш агрегатор общается с сервисами паралельно, но не ассинхронно. Ответ клиент получит только когда оба сервиса отдадут ответ. Ассинхронности тут нет, здесь есть параллельность.

            Под ассинхронностью я понимаю следующий сценарий:

            агрегатор создает «пустую» (т.е. без статьи и коментов, но с хидером, футером, и т.д.) страницу и отдает ее браузеру. Браузер показывает страницу и открывает 2 сокета на получение статьи и комментов к ней. Одновременно агрегатор отсылает запрос каждому из сервисов. Как только ответ пришел, агрегатор обрабатывает его и отсылает браузеру через соответствующий сокет.


            1. VolCh
              23.06.2016 10:59

              Мой агрегатор общается с сервисами асинхронно, он не ждёт ответа от них, а занимается другими делами или нечем не занимается (ничего не делать != делать ничего, ждать события == делать ничего до события)

              Вы описывате трехзвенную архитектуру с асинхронным протоколом между клиентом и фронтом, я описал асинхронную реализацию фронта. Отделяйте протокол общения клиента с сервером (или узла с узлом или шиной в общем случае) и реализацию клиента/сервера/узла/шины. Протокол может быть синхронным, а реализация отправки запросов и обработки ответов асинхронной (часто и у клиента, и у сервера — например аякс запросы к серверу на ноде или просто к нжинкс, ). Речь в совете «никогда не используйте синхронные запросы, если хотите чтобы ваше MSA-приложение работало достаточно быстро!», насколько я понимаю, о реализации механизма отправки исходящих запросов и обработки ответов в целом, но прежде всего на самих микросервисах (в примере — агрегатора), обращающихся к другим микросервисам (в примере — сервисы постов и комментариев).


              1. schroeder
                23.06.2016 11:11

                не-не, конечно же ваш агрегатор что то делает: он ждет. И ждет он последний ответ. Время при этом уходит, в худшем случае вы получаете время отклика на 99.999% совпадающее со временем отклика, как если бы вы тупо опрашивали все сервисы по очереди в for- цикле с ожиданием ответа от каждого сервиса. Ведь клиенту вообще то все равно как там устроен агрегатор, ему важно время появления первого результата.


                1. VolCh
                  23.06.2016 11:39

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

                  Если клиент общается с асинхронным фронтом по синхронному протоколу, то время ответа будет примерно равно максимальному из времён ответа бэков. Если агрегатор синхронный, то время ответа будет примерно равно сумме времён ответа бэков. Если клиент общается с асинхронным фронтом по асинхронному протоколу, то время первого значимого ответа (отклик «ваш запрос принят» за значимый не считаем) будет примерно равно минимальному из времён ответа бэков.

                  В любом случае если клиент асинхронный (например, ajax запрос из браузера), то независимо от протокола и реализации фронта, он (клиент) ответа не ждёт, а занимается своими делами (например, крутит спиннер для пользователя :) или отдаёт управление своему ядру.

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


                  1. schroeder
                    23.06.2016 13:31

                    Вы пишете:

                    И только когда второй ответ будет готов, агрегатор полностью сформирует и отдаст ответ по синхронному протоколу.


                    Чем агрегатор занимается пока не придет второй ответ никого не интересует, главное что время идет.


                    1. VolCh
                      23.06.2016 14:05

                      Как это никого не интересует? Он не работает пока второй ответ не идёт, не потребляет время процессора на циклы ожидания, время как раз не идёт. Об этом же разговариваем — ждёт агрегатор ответа, потребляя ресурсы, или освобождает их до прихода ответа.


  1. aosja
    23.06.2016 14:27

    Решусь оставить это здесь на правах саморекламы. Фреймворк создавался как раз для реализации MSA.


  1. shuron
    23.06.2016 18:53

    Тут прежде всего напрашивается упоминание о тестовой пирамиде… (http://martinfowler.com/bliki/TestPyramid.html)
    Микросервисная архитектура позволяет писать хорошие тесты у базы пирамиды и многое ими покрывать и отлавливать…
    Однако МСА переносит часть сложности в интеграционный слой… и это момент особого внимания в микросервисной архитектуре…
    Тут все должно быть четко и понятно, тогда можно (и нужно) ограничится минимум правильно написаных интеграционных тестов.