Продолжая тему тестирования, сегодня речь о юнит-тестах.
Бытует мнение, что в микросервисах не нужны юнит-тесты. Сложно определить, откуда пошло это заблуждение, но при динамике изменений, свойственной микросервисам, без быстрого выявления ошибок не обойтись.
Не стоит забывать и о том, что юнит-тесты — это и мощная практика дизайна. А трудности с написанием юнит-тестов на существующий код чаще всего связаны именно с проблемами кода и вносить изменения в рамках реализации новых бизнес-фич будет ничуть не проще, а даже и сложнее, чем написать юнит-тест.
Юнит-тесты можно условно поделить на те, что тестируют состояние объекта и те, что тестируют поведение/взаимодействие, используя тест-дублеры.
Первые находятся в центре дизайна, слое доменной логики. Его особенность в том, что если удалось провести качественное проектирование на уровне предметной области, то изменения в него будут вносится все реже и реже(и, скорее всего, точечно), — это стабильная область (в отличие, например, от UI). Следствие из этого — код и нюансы его работы будут забываться, тесты станут стабильнее и выступать в том числе актуальной документацией, а их запуск в рамках CI является формальным подтверждением того, что в сборку не попал регрес.
Вторая категория, — на уровне портов и адаптеров и здесь используются тесты с использованием тест-дублеров. Это другой слой стратегии тестирования. В нем проверяется любая логика, подготавливающая запросы или обрабатывающая ответы от внешних систем (в том числе баз данных), используя вместо них тест-дублеры. Это позволяет обспечить надежную, быструю и повторяемую проверку циклов «запрос-ответ».
Если планируется переход от монолита к микросервисам, то юнит-тесты с использованием дублеров могут разделиться на две части. Чтобы лучше это понять, следует обратиться к шаблону «Микросервисное шасси/Фреймворк микросервиса» (Microservices Chassis - https://microservices.io/patterns/microservice-chassis.html) — от вынесение общей инфраструктурной функциональности в отдельный фреймворк. Тесты общего назначения могут уйти в кодовую базу фреймворка и поддерживаться отдельно, что снижает общую сложность управления тестами и сокращает время их выполнения (в CI сервиса выполняются юнит-тесты сервиса, а юнит тесты фреймворка выполняются в своем CI).
В сухом остатке имеем набор микросервисов, каждый со своим набором юнит-тестов, выполняющихся в собственном пайплайне, часть из которых может быть вынесена в отдельный пайплайн микросервисного фреймворка. В рамках пайплайна тесты на домен более стабильные, чем тесты портов и адаптеров и мы держим их отдельно (не смешивая в коде самих тестов), чтобы не привнести более низкую стабильность в тесты доменной логики.
Иногда код настолько плох, что юнит-тест не пишется, с какой стороны к нему не подойди. Был случай, когда команде буквально приказали начать писать юнит-тесты, которых никто никогда не писал, а системе (это была java) больше 10 лет. За два месяца не было написано ни одного теста. Это тот случай, когда типовая стратегия тестирования для greefield не подходит и следует рассмотреть альтернативы. О них напишу после еще нескольких постов о видах тестирования в применимости к микросервисам (хотя, это актуально вне зависимости от архитектурного стиля).
Бытует мнение, что в микросервисах не нужны юнит-тесты. Сложно определить, откуда пошло это заблуждение, но при динамике изменений, свойственной микросервисам, без быстрого выявления ошибок не обойтись.
Не стоит забывать и о том, что юнит-тесты — это и мощная практика дизайна. А трудности с написанием юнит-тестов на существующий код чаще всего связаны именно с проблемами кода и вносить изменения в рамках реализации новых бизнес-фич будет ничуть не проще, а даже и сложнее, чем написать юнит-тест.
Юнит-тесты можно условно поделить на те, что тестируют состояние объекта и те, что тестируют поведение/взаимодействие, используя тест-дублеры.
Первые находятся в центре дизайна, слое доменной логики. Его особенность в том, что если удалось провести качественное проектирование на уровне предметной области, то изменения в него будут вносится все реже и реже(и, скорее всего, точечно), — это стабильная область (в отличие, например, от UI). Следствие из этого — код и нюансы его работы будут забываться, тесты станут стабильнее и выступать в том числе актуальной документацией, а их запуск в рамках CI является формальным подтверждением того, что в сборку не попал регрес.
Вторая категория, — на уровне портов и адаптеров и здесь используются тесты с использованием тест-дублеров. Это другой слой стратегии тестирования. В нем проверяется любая логика, подготавливающая запросы или обрабатывающая ответы от внешних систем (в том числе баз данных), используя вместо них тест-дублеры. Это позволяет обспечить надежную, быструю и повторяемую проверку циклов «запрос-ответ».
Если планируется переход от монолита к микросервисам, то юнит-тесты с использованием дублеров могут разделиться на две части. Чтобы лучше это понять, следует обратиться к шаблону «Микросервисное шасси/Фреймворк микросервиса» (Microservices Chassis - https://microservices.io/patterns/microservice-chassis.html) — от вынесение общей инфраструктурной функциональности в отдельный фреймворк. Тесты общего назначения могут уйти в кодовую базу фреймворка и поддерживаться отдельно, что снижает общую сложность управления тестами и сокращает время их выполнения (в CI сервиса выполняются юнит-тесты сервиса, а юнит тесты фреймворка выполняются в своем CI).
В сухом остатке имеем набор микросервисов, каждый со своим набором юнит-тестов, выполняющихся в собственном пайплайне, часть из которых может быть вынесена в отдельный пайплайн микросервисного фреймворка. В рамках пайплайна тесты на домен более стабильные, чем тесты портов и адаптеров и мы держим их отдельно (не смешивая в коде самих тестов), чтобы не привнести более низкую стабильность в тесты доменной логики.
Иногда код настолько плох, что юнит-тест не пишется, с какой стороны к нему не подойди. Был случай, когда команде буквально приказали начать писать юнит-тесты, которых никто никогда не писал, а системе (это была java) больше 10 лет. За два месяца не было написано ни одного теста. Это тот случай, когда типовая стратегия тестирования для greefield не подходит и следует рассмотреть альтернативы. О них напишу после еще нескольких постов о видах тестирования в применимости к микросервисам (хотя, это актуально вне зависимости от архитектурного стиля).
FailoverConf, 21 апреля, онлайн, участие бесплатное.
Бомбический список участников и докладов, регистрируемся!
Бомбический список участников и докладов, регистрируемся!
Референсная архитектура для микросервисов. Напоминание о том, как много всего нужно не забыть.
В микросервисах, живущих в облаках кеширование может серьезно снизить стоимость решения. Для «не облаков» это не очевидно, но там, где оплата идет за используемые ресурсы (процессор, память, доступ к данным) — выгода просчитывается достаточно просто, главное не забыть о других атрибутах качества, таких как безопасность, степень актуальности данных, производительность и стоимость поддержки самого кеша.
The Adventures of Microservice Episode 1 – The Birth of Microservice
The Adventures of Microservice Episode 2 – The Kickoff Meeting
The Adventures of Microservice Episode 3 (новее пока нет) – In the Data Center
Монолитра!)
Монолитра!)
Cаммари отчета «Engineering Reliable Mobile Applications» от Саши Поломодова:
https://medium.com/@alexanderpolomodov/sre-практики-в-разработке-мобильных-приложений-c9313d915e74
https://medium.com/@alexanderpolomodov/sre-практики-в-разработке-мобильных-приложений-c9313d915e74
Medium
SRE практики в разработке мобильных приложений
Компания Google щедро делится своими практиками на тему построения надежных сервсисов (подробнее в источниках 2, 3, 4), а в прошлом году…
Когда мы говорим о том, какими микросервисы должны быть с концептуальной точки зрения, мы всегда держим в уме и повторяем как мантру: «слабая связанность» и «сильное сцепление». Всегда.
И маршируя по Legacy Street за переход к более гибкой архитектуре, на наших транспарантах будут именно словосочетания «слабая связанность» и «сильное сцепление» :)
Что главное? Возможность внесения изменений и развертывание сервиса без необходимости внесения изменений в любую другую часть системы. Оно же — Low Coupling.
Сильное сцепление (High Cohesion) — я, как кем бы я ни был, хочу, чтобы связанное поведение находилось в одном месте, внутри некой границы, которая имела бы как можно более слабую связь с другими границами.
Вот тут появляется ограниченный контекст (Bounded Context) или иначе — конкретная ответственность, обеспечиваемая четко обозначенными границами.
И если мы хотим перейти от монолита к микро, то мы сначала очень аккуратно выделяем контексты, определяем модель (внутреннюю для контекста и общую, для общения), повышаем модульность системы. Уверены? Выносим модуль в сервис.
И думаем о сервисах в терминах бизнес-возможностей. Сначала «Чем контекст (модуль, сервис) занимается и какие услуги предоставляет?», затем «Что (какие данные, внутренние или из других контекстов) ему нужны?»
И маршируя по Legacy Street за переход к более гибкой архитектуре, на наших транспарантах будут именно словосочетания «слабая связанность» и «сильное сцепление» :)
Что главное? Возможность внесения изменений и развертывание сервиса без необходимости внесения изменений в любую другую часть системы. Оно же — Low Coupling.
Сильное сцепление (High Cohesion) — я, как кем бы я ни был, хочу, чтобы связанное поведение находилось в одном месте, внутри некой границы, которая имела бы как можно более слабую связь с другими границами.
Вот тут появляется ограниченный контекст (Bounded Context) или иначе — конкретная ответственность, обеспечиваемая четко обозначенными границами.
И если мы хотим перейти от монолита к микро, то мы сначала очень аккуратно выделяем контексты, определяем модель (внутреннюю для контекста и общую, для общения), повышаем модульность системы. Уверены? Выносим модуль в сервис.
И думаем о сервисах в терминах бизнес-возможностей. Сначала «Чем контекст (модуль, сервис) занимается и какие услуги предоставляет?», затем «Что (какие данные, внутренние или из других контекстов) ему нужны?»
В последнее время GraphQL стали предрекать смерть от http/2. Ниже мои мысли на этот счет.
Давным-давно сайты были легкими и HTTP/1 отлично гонял свои килобайты по стабильному соединению (один ресурс — одно TCP соединение). Шло время, а вместе с ним увеличивались объемы данных и росло количество запросов. Это порождало большие накладные расходы на дорогие операции открытия TCP. Помню, как мы старались склеивать картинки, лишь бы сократить их количество 🙂
Стало совсем невыносимо и появился HTTP/1.1 с persistent (keep-alive) connections и pipeline. Мы смогли отсылать несколько запросов и ответов в одно соединение и это было реально круто! Вот только один медленный запрос блокировал последующие, а бездействующее соединение все же потребляет какие-никакие ресурсы.
Что сделал GraphQL? Он позволил клиенту запросить все ресурсы за раз. Клиент сам решает, какие данные ему нужны, запрашивает их и получает одним пакетом. Вспомним времена HTTP/1, когда для заполнения одного экрана нужно было сделать несколько реквестов, в каждом ответе по 15 полей из которых нужных — по 2-3 максимум. Знатный оверхед.
В HTTP/2 (к слову — он стал бинарным, что уже сделало его более быстрым) появилась новая фича — request multiplexing. Эта фича позволяет точно так же, как в HTTP/1.1 использовать одно соединение для множества запросов, но только здесь все запросы выполняются параллельно, что исключает блокировку последующих запросов одним медленным.
Фактически в HTTP/2 заложено решение той проблемы, которую решал GraphQL с точки зрения производительности. И если брать скорость как единственный критерий, то GraphQL уже не так и нужен. Но на GraphQL можно посмотреть шире — он сдвинул парадигму в сторону клиентоцентричности.
На уровне обращения к API всегда бэк решал, что отдать фронту. «Хочешь получить имя пользователя? Вот готовый сервис. Возвращающий еще три десятка параметров (за которыми вызовы десятка микросервисов).». И вот с помощью GraphQL наконец фронт может сам решать, какие данные ему нужны и запрашивать только их (а значит будут вызваны только микросервисы, возвращающе конкретные данные). Это фундаментальный сдвиг парадигмы.
Можно долго спорить о надежности GraphQL, но то, что он привнес своего рода клиентоцентричность в процесс разработки — этому можно только порадоваться, так что если его кто и похоронит GraphQL, то не HTTP/2, а другой инструмент, развивающий эту парадигму.
Давным-давно сайты были легкими и HTTP/1 отлично гонял свои килобайты по стабильному соединению (один ресурс — одно TCP соединение). Шло время, а вместе с ним увеличивались объемы данных и росло количество запросов. Это порождало большие накладные расходы на дорогие операции открытия TCP. Помню, как мы старались склеивать картинки, лишь бы сократить их количество 🙂
Стало совсем невыносимо и появился HTTP/1.1 с persistent (keep-alive) connections и pipeline. Мы смогли отсылать несколько запросов и ответов в одно соединение и это было реально круто! Вот только один медленный запрос блокировал последующие, а бездействующее соединение все же потребляет какие-никакие ресурсы.
Что сделал GraphQL? Он позволил клиенту запросить все ресурсы за раз. Клиент сам решает, какие данные ему нужны, запрашивает их и получает одним пакетом. Вспомним времена HTTP/1, когда для заполнения одного экрана нужно было сделать несколько реквестов, в каждом ответе по 15 полей из которых нужных — по 2-3 максимум. Знатный оверхед.
В HTTP/2 (к слову — он стал бинарным, что уже сделало его более быстрым) появилась новая фича — request multiplexing. Эта фича позволяет точно так же, как в HTTP/1.1 использовать одно соединение для множества запросов, но только здесь все запросы выполняются параллельно, что исключает блокировку последующих запросов одним медленным.
Фактически в HTTP/2 заложено решение той проблемы, которую решал GraphQL с точки зрения производительности. И если брать скорость как единственный критерий, то GraphQL уже не так и нужен. Но на GraphQL можно посмотреть шире — он сдвинул парадигму в сторону клиентоцентричности.
На уровне обращения к API всегда бэк решал, что отдать фронту. «Хочешь получить имя пользователя? Вот готовый сервис. Возвращающий еще три десятка параметров (за которыми вызовы десятка микросервисов).». И вот с помощью GraphQL наконец фронт может сам решать, какие данные ему нужны и запрашивать только их (а значит будут вызваны только микросервисы, возвращающе конкретные данные). Это фундаментальный сдвиг парадигмы.
Можно долго спорить о надежности GraphQL, но то, что он привнес своего рода клиентоцентричность в процесс разработки — этому можно только порадоваться, так что если его кто и похоронит GraphQL, то не HTTP/2, а другой инструмент, развивающий эту парадигму.
Легким движением руки ноут с 8GB превращается в микро датацентр (с подробной инструкцией)
В микросервисах нередко I/O (например — работа с базой) операции рассматриваются как часть Unit’а в контексте Unit-тестирования. Таким образом, Unit-тесты начинают больше походить на интеграционные, интеграционные тесты — на тесты на живой системе в проде, а прод-тесты — на мониторинг и исследование. И в целом уже несколько раз приходилось переопределять состав юнита (минимальной, атомарной единицы). Ведь если сервис в 95% случаем обращается к базе и фактически это весь его код, то появлсяется смысл рассматривать вызов базы как часть юнита, даже несмотря на возможные side effect’ы с сетью и тем самым получить больший outcome от тестов за счет снижения стоимости поддержки (отсутствия заглушек и двух наборов тестов), фокусируясь на бизнес-функциях.
У многих разработчиков и архитекторов законно возникает множество вопросов к согласованности данных в микросервисах. Некоторые приходят к паттерну SAGA и вопросов становится еще больше 🙂 Saga из тех паттернов, к которым интуитивно подходит я бы не советовал по двум причинам:
1. Она все-таки сложна в реализации и
2. Нередко затрагивает достаточно важные бизнес-процессы в распределенной, событийной системе
То есть вероятность завалить всё высокая, а исправить быстро (и ладно бы исправить — банально понять в чем дело) получается далеко не всегда.
А ведь больше половины ответов на чаще всего возникающие вопросы содержатся прям вот в том самом документе, который её и породил: https://www.cs.cornell.edu/andru/cs711/2002fa/reading/sagas.pdf
И практически все статьи повторяют в том или ином виде описанное в этой статье (иногда дополняя технологическими особенностями, иногда приводя примеры из конкретных предметных областей). Но я все же считаю, что если уж и изучать что-то сложное, то начинать с первоисточников (если первоисточники не дискридитированы, но тогда и о чтении базирующихся на первоисточнике материалов стоит задуматься), после чего можно переходить к интерпретациям.
Кто еще не читал — must read!
1. Она все-таки сложна в реализации и
2. Нередко затрагивает достаточно важные бизнес-процессы в распределенной, событийной системе
То есть вероятность завалить всё высокая, а исправить быстро (и ладно бы исправить — банально понять в чем дело) получается далеко не всегда.
А ведь больше половины ответов на чаще всего возникающие вопросы содержатся прям вот в том самом документе, который её и породил: https://www.cs.cornell.edu/andru/cs711/2002fa/reading/sagas.pdf
И практически все статьи повторяют в том или ином виде описанное в этой статье (иногда дополняя технологическими особенностями, иногда приводя примеры из конкретных предметных областей). Но я все же считаю, что если уж и изучать что-то сложное, то начинать с первоисточников (если первоисточники не дискридитированы, но тогда и о чтении базирующихся на первоисточнике материалов стоит задуматься), после чего можно переходить к интерпретациям.
Кто еще не читал — must read!
Статья «Размер микросервиса»
Есть много статей о размерах, много о изоляции, но мне пока не встречались статьи (пропустил?), в которых изоляция и размер рассматривались бы в едином контексте. Изложил свои мысли на этот счет как обобщение практического опыта.
Идея простая — изоляция важнее размера.
Есть много статей о размерах, много о изоляции, но мне пока не встречались статьи (пропустил?), в которых изоляция и размер рассматривались бы в едином контексте. Изложил свои мысли на этот счет как обобщение практического опыта.
Идея простая — изоляция важнее размера.
В субботу пройдет TechTrain, «бесплатный небольшой онлайн-фестиваль, объединяющий разработчиков, инженеров и им сочувствующих».
По теме канала будет про тестирование приложений с потоковыми процессами (kafka) от Виктора Гамова (confluent) и об управлении зависимостями в CI/CD от Олега Ненашева(cloudbees).
По теме канала будет про тестирование приложений с потоковыми процессами (kafka) от Виктора Гамова (confluent) и об управлении зависимостями в CI/CD от Олега Ненашева(cloudbees).
Как Spotify мигрировал свои 1200 микросервисов в Google Cloud.
Просто грандиознейшее переселение микросервисов.
— Сформировали команду миграции
— Разделили миграцию на две части: миграция сервисов и миграция данных
— Визуализировали все сервисы, цветами помечая перенесённые (в статье написано зачем)
— Затем разделили стратегию миграции сервисов еще на две: миграция сервисов и миграция пользовательского трафика
— Мигрировали итерациями (1-2 недельными)
— Команда миграции втихую ломала смигрированное, чтобы посмотреть на реакцию разработчиков 😈
По ссылке статья и видео (англ)
Просто грандиознейшее переселение микросервисов.
— Сформировали команду миграции
— Разделили миграцию на две части: миграция сервисов и миграция данных
— Визуализировали все сервисы, цветами помечая перенесённые (в статье написано зачем)
— Затем разделили стратегию миграции сервисов еще на две: миграция сервисов и миграция пользовательского трафика
— Мигрировали итерациями (1-2 недельными)
— Команда миграции втихую ломала смигрированное, чтобы посмотреть на реакцию разработчиков 😈
По ссылке статья и видео (англ)
Внезапно прошла волна репостов статьи «Microservices considered harmful». Ссылка ниже.
Первая мысль из статьи
>>Если у вас спагетти в монолите, то при делении вы получите «spaghetti over HTTP».
Все правильно, только при чем тут микросервисы? Не устаю повторять две вещи:
1. «Выбирайте микросервисы за их преимущества, а не потому, что код монолита ужасен»
2. «Не нарезайте монолит AS IS, проведите моделирование с самого начала, постройте корректную модель предметной области, выделите независимые модули/пакеты внутри монолита и затем выносите микросервисы»
Вторая мысль из статьи
>>Производительность хуже, потому что пакеты бегают по сети.
Сравниваем время обработки сообщений и время на передачу данных. Потеряли на передаче 5 секунд, выиграли на параллельной обработке 10 минут. Так это работает.
>>Масштабируемость хуже, потому что… лучше масштабировать все целиком и появляются новые точки отказа.
Есть великолепные паттерны и инструменты изоляции сбоев. И независимое масштабирование дает космический эффект в облаках, где оплата за использованные ресурсы.
А вот вывод отличный: «keep in mind that almost all technical challenges (code modularity, scalability, single point of failure…) will not be magically solved by using microservices». Все по делу.
———
Микросервисы — не панацея, они сложны, но у них есть существенные преимущества. Эти преимущества не нужны всем, выбор микросервисов должен быть очень прагматичным выбором. Монолит может быть качественно и модульно написан. Микросервисы нужны тогда, когда требуется независимая поставка/независимая замена/независимое развитие/независимое масштабирование/независимый выбор технологий и это действительно дает серьезное конкуретное преимущество. А не ради хайпа 🙂
Первая мысль из статьи
>>Если у вас спагетти в монолите, то при делении вы получите «spaghetti over HTTP».
Все правильно, только при чем тут микросервисы? Не устаю повторять две вещи:
1. «Выбирайте микросервисы за их преимущества, а не потому, что код монолита ужасен»
2. «Не нарезайте монолит AS IS, проведите моделирование с самого начала, постройте корректную модель предметной области, выделите независимые модули/пакеты внутри монолита и затем выносите микросервисы»
Вторая мысль из статьи
>>Производительность хуже, потому что пакеты бегают по сети.
Сравниваем время обработки сообщений и время на передачу данных. Потеряли на передаче 5 секунд, выиграли на параллельной обработке 10 минут. Так это работает.
>>Масштабируемость хуже, потому что… лучше масштабировать все целиком и появляются новые точки отказа.
Есть великолепные паттерны и инструменты изоляции сбоев. И независимое масштабирование дает космический эффект в облаках, где оплата за использованные ресурсы.
А вот вывод отличный: «keep in mind that almost all technical challenges (code modularity, scalability, single point of failure…) will not be magically solved by using microservices». Все по делу.
———
Микросервисы — не панацея, они сложны, но у них есть существенные преимущества. Эти преимущества не нужны всем, выбор микросервисов должен быть очень прагматичным выбором. Монолит может быть качественно и модульно написан. Микросервисы нужны тогда, когда требуется независимая поставка/независимая замена/независимое развитие/независимое масштабирование/независимый выбор технологий и это действительно дает серьезное конкуретное преимущество. А не ради хайпа 🙂