Comments 173
Еще начиная с известного спора Линуса с Таненбаумом о микросервисной архитектуре, я не понимал почему их так сильно противопоставляют. Даже если мы имеем монолитный сервис, все равно у нас есть такое же разделение на микросервисы, только выполненное на уровне исходного кода. Это декомпозиция кода на модули, методы и определение четких интерфейсов взаимодействия между разными вызовами.
Так ли это важно, на каком уровне выполнена декомпозиция на отдельные независимо работающие части кода?
Я не автор, но отмечу, что на мой взгляд микросервисная архитектура в первую очередь решает кадрово-организационный вопрос быстрого роста команды, всё остальное вторично.
Так ли это важно, на каком уровне выполнена декомпозиция на отдельные независимо работающие части кода?
микросервисы — это про горизонтальное масштабирование. Монолит плохо и нерационально масштабируется.
В большинстве случаев декомпозиция в монолите выполнена намного хуже, чем в микросервисах. Типичный пример — у каждого stateful микросервиса обязательно собственная БД. В монолитах использование десятка разных БД (хотя бы на уровне разных логинов и database в одном MySQL) практикуется… практически никогда. Помимо изоляции данных, далеко не каждый язык программирования позволяет действительно надёжно изолировать разные части монолита друг от друга, не на уровне соглашений между разработчиками, а с жёстким контролем средствами языка (напр. в Go поддержка internal пакетов появилась всего 3 года назад).
В результате контроль изоляции разных частей монолита остаётся на ответственности и внимательности самих разработчиков, что, очевидно, не работает и не может работать с достаточно большими приложениями, командами, и дедлайнами.
Так что в теории да, можно делать микросервисы внутри монолита (встроенные микросервисы), но на практике это пока редко встречается.
OSGI как раз мотивирует создавать такие приложения и в случае необходимости можно разнести сервисы из одного процесса в несколько на разных узлах используя DOSGI.
В большинстве случаев декомпозиция в монолите выполнена намного хуже, чем в микросервисах.
Разве декомпозиция напрямую зависит от выбора архитектурного паттерна монолит/SOA?
Значит ли это, что мир монолитных приложений погряз в проблемах композиции?
В монолитах использование десятка разных БД (хотя бы на уровне разных логинов и database в одном MySQL) практикуется… практически никогда.
На какие практики или исследования вы опираетесь?
Исходя из моего опыта продуктовой разработки ваше утверждение ложно.
не каждый язык программирования позволяет действительно надёжно изолировать разные части монолита друг от друга, не на уровне соглашений между разработчиками, а с жёстким контролем средствами языка
Поясните, пожалуйста, почему SOLID и GRASP не подходят, а требуются какие-то жесткие средства языка.
Обладают ли этими жесткими средствами C++/C/C#/JAVA?
в Go поддержка internal пакетов появилась всего 3 года назад
Можете пояснить ваш use-case использования internal packages в контексте замены SOA на монолит?
контроль изоляции разных частей монолита остаётся на ответственности и внимательности самих разработчиков, что, очевидно, не работает и не может работать с достаточно большими приложениями, командами, и дедлайнами.
Есть какие-то исследования, что все ПО написанное до второго бума SOA «не работает»?
Chromium/V8 тоже не работают?
Ну что Вам сказать… В теории Вы правы, это может работать так, как Вы описываете. Но, хотя в теории теория и практика различаться не должны, на практике они различаются.
Разве декомпозиция напрямую зависит от выбора архитектурного паттерна монолит/SOA?
В теории — нет. Но на практике эта зависимость однозначно наблюдается. Проблема в том, что если у разработчиков есть возможность (а в монолите — она обычно есть) нарушить архитектуру и сделать что-то неправильно, создать какие-то связи между компонентами, которых быть не должно — они сделают это, причём скорее рано, нежели поздно. Из-за отсутствия квалификации, понимания, или просто времени на то, чтобы сделать "правильно". В SOA намного сложнее и дольше создать такие некорректные связи, потому что для того, чтобы добраться до недоступного через API функционала стороннего сервиса из другого сервиса сначала надо изменить и выкатить первый сервис, добавив в него дополнительное API. Эта дополнительная сложность создаёт препятствие, которое довольно эффективно мешает портить текущую архитектуру. Конечно, при наличии должной степени упёртости можно и это преодолеть, но обычно архитектуру портят не из желания сделать гадость, а из желания сделать проще/быстрее — и вот в SOA делать такие вещи уже ни разу не проще и не быстрее, что очень помогает.
Значит ли это, что мир монолитных приложений погряз в проблемах композиции?
Не то, чтобы буквально погряз… но да, архитектурный паттерн big ball of mud определённо появился в монолитах, и, насколько мне известно, в микросервисах пока не встречался.
На какие практики или исследования вы опираетесь?
Исходя из моего опыта продуктовой разработки ваше утверждение ложно.
Лично я прямо сейчас пишу что-то вроде монолита/встроенных микросервисов, и у меня в одном приложении несколько изолированных логинов и database в MySQL. Тем не менее, где либо кроме своих собственных проектов я этого подхода не видел, ни в компаниях, ни в опенсорс-проектах. Так что хотелось бы узнать про Ваш опыт подробнее — где (кроме собственных проектов) Вы такое видели, и как часто это встречается.
Поясните, пожалуйста, почему SOLID и GRASP не подходят, а требуются какие-то жесткие средства языка.
Потому, что наличие в монолите какой-то библиотеки/класса с публичным интерфейсом ещё не означает, что ими можно пользоваться из любой части монолита.
Обладают ли этими жесткими средствами C++/C/C#/JAVA?
Я не эксперт в этих языках, так что с гарантией не скажу. Насколько мне известно — скорее нет.
Можете пояснить ваш use-case использования internal packages в контексте замены SOA на монолит?
Пакеты в подкаталоге internal/ в Go не доступны никому в родительских/соседних каталогах, что позволяет гарантировать что вызывать эти библиотеки/классы из другой части монолита не получится, если только не открыть к ним явно доступ через API пакета находящегося в том же каталоге, что и internal. Этот подход вполне соответствует стилю работы с микросервисами — не важно, какой код доступен внутри микросервиса, снаружи есть доступ только к тому, к чему явно предоставили API.
Есть какие-то исследования, что все ПО написанное до второго бума SOA «не работает»?
Chromium/V8 тоже не работают?
Не надо передёргивать, я лично таких утверждений не делал. Если хотите услышать утверждения, с которым можно всласть поспорить — пожалуйста:
- до определённой границы сложности монолит писать проще микросервисов, после этой границы проще писать микросервисы
- микросервисы добавляют заметное количество дополнительной сложности из-за сетевого взаимодействия, но эта сложность константная во всех проектах и не увеличивается с развитием проекта, в отличие от ситуации с большинством монолитов, что и является причиной предыдущего утверждения
- большинству проектов не нужны возможности писать микросервисы на разных ЯП и запускать их на разных серверах, нужна возможность быстро развивать большой проект, в идеале — монолит… кандидатом для решения этой задачи являются "встроенные микросервисы", которые позволяют получить идентичный микросервисам уровень изоляции между командами разработчиков и кодом внутри монолита, избегая дополнительной сложности из-за сетевого взаимодействия… но чтобы это работало крайне желательно иметь поддержку на уровне инструментов — хоть языка, хоть линтера, хоть pre-commit хуков, но чтобы работало автоматически и мешало из кода одного модуля лазить в данные или код другого модуля мимо его публичного API
По моим наблюдениям, микросервисы требуют заметно большей квалификации от архитектора и меньшей квалификации от разработчиков, чем небольшие монолиты… но большие качественные монолиты, которые не тонут в растущей сложности и не превращаются в большой комок грязи, требуют такой же (а может и большей) квалификации архитектора как микросервисы плюс намного большей квалификации разработчиков, чем микросервисы. Иными словами, без хорошего архитектора к микросервисам подступаться бессмысленно. А с хорошим архитектором и средней командой разработчиков можно осилить микросервисы, но не большой и сложный монолит. Ещё иными словами: нормально написать большой монолит тупо дороже, чем то же самое на микросервисах. Идея со встроенными микросервисами в том, что они должны оказаться заметно дешевле обычных микросервисов, ценой потери незначительных для большинства проектов возможностей (разные ЯП в одном проекте, запуск разных частей проекта на разных серверах).
Не то, чтобы буквально погряз… но да, архитектурный паттерн big ball of mud определённо появился в монолитах, и, насколько мне известно, в микросервисах пока не встречался.
В микросервисах он мигрировал на следующий уровень абстракции :-) и может быть найден в скриптах деплоя и конфигах.
можно делать микросервисы внутри монолитаТолько… в таком случае они будут называться Bounded Context.
Какой-бы не был супер-монолит, с передачей всего по ссылкам и прочими ультра-оптимизациями, настанет момент, когда одного сервера перестанет хватать. Но всегда есть функциональность, которую нельзя просто накопипастить по серверам без взаимодействия между ними (например, регистрация аккаунта с проверкой уже занятых логинов), или ведение сессий пользователя. Появится необходимость специализации серверов, и это ведёт к сервисам.
что там может быть такого в обычных веб-приложениях, что нагружало бы целые кластера серверов. Не сталкивался с таким.
А что для вас обычное веб-приложение? Если мы захотим вполне банальный агрегатор отелей или агрегатор авиаперелетов и будем максимально хранить кэши в памяти у нас быстро память закончится, ибо комбинаторный взрыв. Просто вы вероятно не работали в компаниях с достаточно большой аудиторией.
неужели там такие гигантские объёмы
Ну давайте посчитаем, гостиниц в Москве скажем 1 тыс. (учитываем всякие частные гостиницы/аппартменты/хостелы и т.д.), вариантов размещения (общий номер, одноместный с общей ванной, одноместный с собственной ванной, двухместных, люкс и т.д.) до 20 штук (20 тыс. записей), нам нужно хранить цены с завтраком и без (40 тыс.), с возможностью отмены и без (80 тыс), на каждый день в году (30 млн), так же возможны скидки отеля при бронировании нескольких дней подряд (все возможные интервалы в течении года — много, ну скажем еще на 10 умножим (250 млн), на нужны цена так же на разное кол-во взрослых и детей в номере (еще на 10 — 500 млн), цены могут приходить как от самого отеля, так и от систем бронирования (вроде букинга) — скажем еще с 10 партнеров (5 млд. записей), каждая запись кроме цены/валюты еще (как минимум) должна хранить id на сайт партнера и время до которого она валидна. На самом деле, скорее всего параметров будет больше.
В общем, несколько десятков Гб только на кеш цен одной Москвы, причем по хорошему его нужно держать в памяти, ибо в большинство пользователей просит самые дешевые отели на данные даты и параметры, то есть каждый раз придется поднимать данные всех отелей и партнеров на эти даты, диск загнется, ну пользователь не будет ждать более нескольких секунд.
Теперь маштабируйте систему хотя до уровня всех городов России (я уже не говорю до всего мира) и попытайтесь засунуть все это в один сервер. В теории, это можно попытаться как-то оптимизировать, но все равно данных дофига и это лишь цены.
Для авиакомпаний можете сами посчитать, например сколько параметров может быть только для полета из одной Москвы.
Просто «сложность» ядра, переносится в «сложность» взаимодействия между микроядрами.
И он прав.
В микросервисной архитектуре сложность «монолита» переносится на «сложность» взаимодействия между микросервисами.
Тащем-та Танненбаум с Торнвальдсом спорили по поводу микроядро vs монолит относительно разработки операционных систем. Понятия микросервисы, в те достопамятные времена, просто не существовало. Ну а сейчас такой спор просто бессмысленен, с появлением микроядер следующих поколений(L3, L4, QNX Neutrino etc).
[/Зануда mode]
А в том, что очень многие
Дополнительная проблема заключается в том, что каждый сервис испытывает разную нагрузку. Некоторые сервисы обрабатывали несколько событий в день, а другие – тысячи в секунду.
По началу на каждый чих создавался микросервис, печать документов — сервис, внешние заявки — сервис, плохие клиенты — сервис. Сейчас их большое количество, не 140 конечно, но около 25, хорошо они хоть друг от друга не так часто зависят.
В общем если сервисов около 20, то админить и тестировать их не сложно, развернуть весь стек приложений тоже, масштабировать и тп., но вот если их больше — то начинается боль, особенно если новый человек хочет разобраться в этом.
— А какой уж там сервис отвечает за X функционал? и какой — за Y?
— Я не мог пол дня настроить окружение, блин они еще друг от друга зависят.
В общем мой совет — не перебарщивать с количеством и делить сервисы на роли.
Т.е. настройка и конфигурация должны настраиваться не в ручную, а быть в виде программного кода. С обязательным покрытием тестами.
Со всеми плюшками DI, тогда зависимости для развертывания dev-окружения можно mock-ировать
А так. Какая разница, что монолит, что микросеврисы.
В микросерисы хотя бы можно «есть по частям», т.е. в начале понять как работает один микросервис и поправить его (при этом вероятность что-то сломать минимальна), а потом все остальное.
А в монолите у вас есть куча говнокода, в темные уголки которого не заглядывают годами.
И при любом изменении нужно молиться, что есть тест, который может проверить, что что-то сломалось.
Как будто в микросервисах говнокода не бывает...
В монолите у меня есть хоткей чтобы увидеть кто еще этот код использует.
А при хорошей архитектуре можно и в монолите кусочек спокойно переписать.
Не надо пожалуйста переводить термины, не сразу понял, что
Полезная нагрузка = payload
Конечная точка = endpoint
Такое ощущение, что DI к в команде не практикуется. :-)
Микросервисы требуют большей дисциплины, большей команды и большей компетенции. Если чего-то из этого не хватает, то будут проблемы.
А самая большая проблема в таких проектах, по моему опыту — это донести до менеджера, что не бывает гибкой разработки проектов (agile) без рефаторинга. Иногда надо остановиться и существенно перетряхнуть внутренности проекта, что может занимать немало времени.
Был опыт развития проекта, построенного на микросервисной архитектуре. Окончательно меня бомбануло, когда разработчик несколько дней дорабатывал API сервиса для того, чтобы сделать подобие LEFT JOIN между двумя сервисами. И работало это на 2-3 порядка медленнее, чем запрос в базу. После было принято решение объдинить сервисы в монолит. Жалею только о том, что не сделал это сразу, как пришёл в проект, много времени было потеряно зря. Но, безусловно, это не проблема концепции, это проблема архитектуры проекта и изменчивость требований заказчика.
Сама по себе микросервисная архитектура не плохая и не хорошая, просто на хайпе её суют куда попало, в каждый «утюг», это же, блин, модно… А каждый инструмент надо использовать с умом, там где он реально помогает решать задачу.
Спасибо автору за то, что поделился опытом. Здорово, что не упёрлись рогом в концепцию, а стали искать другие решения. Может они ещё и вернутся к микросервисной архитектуре, но в другом виде…
Во-первых, просто не любить микросервисы или любить микросервисы, это непрофессионально.
Почему?
Во-вторых, нужно понимать когда ее применяют, как условия так и место в жизненном цикле продукта. Какие проблемы она решает. Какие у нее антипаттерны и ограничения.
Об этом у меня написано:
Сама по себе микросервисная архитектура не плохая и не хорошая, просто на хайпе её суют куда попало, в каждый «утюг», это же, блин, модно… А каждый инструмент надо использовать с умом, там где он реально помогает решать задачу.
Потому-что это инструментарий. Это не вопрос личных симпатий и антипатий.Но почему у меня не может быть любимой лопаты? ))))
Беда в том, что автор оригинальной статьи не только не понимает когда использовать технологию, но и не понимает ее вообще.Мне кажется, это проблема людей вообще, мы не можем всё знать и совершаем ошибки… Автор поделился своим опытом, сообщество обсудило, а автор, думаю, вынесет из обсуждения что-то полезное для себя.
Вопрос не в том, может ли лопата быть любимой, а в том, что это не должно влиять на определение того, насколько лопата подходит для решения определённой задачи. Не нравятся микросервисы — просто не участвуйте в проектах, для которых микросервисная архитектура подходит лучше других. А вот навязывать этим проектам другую архитектуру из соображений личных симпатий — как раз непрофессионально. Впрочем, лично Вы в изначальном комментарии таких утверждений явно не делали, так что возможно sotnikdv немного поспешил с выводами.
Лопата может быть десять раз любимой, но сено собирать лучше вилами...
Беда в том, что автор оригинальной статьи не только не понимает когда использовать технологию, но и не понимает ее вообще.
Это случайно не вы Джессике Фразелли недавно объясняли как контейнеры работают?
Если нужен был LEFT JOIN и была база, то почему бы не написать микросервис, который это делает?!
Т.е. если можно (и это проще) использовать готовые микросервисы, то надо ими пользоваться.
Если проще написать «с нуля», то почему бы и не написать «с нуля»?!
Тут вопрос к «архитектору», ну или кто отвечал за общую структуру проекта.
Почему он решил, что надо делать так?!
Но, безусловно, это не проблема концепции, это проблема архитектуры проекта и изменчивость требований заказчика.
Как реализовать микросервис, это задача программиста.
Если проще/удобнее напрямую обратиться к БД, то почему бы и да.
Просто «архитектор» мог поставит жесткие условия, что сделать надо так и никак иначе.
ИМХО, «архитектор» должен говорить, «что» будет делать микросервис, а «как» пусть программист сам решит.
Благо микросервис должен быть таким, чтобы он был понятен любому программисту.
Соответственно переписать его может любой программист.
Если, например, вы считаете, что проще/быстрее/удобнее использовать 1 один запрос к БД. То микросервисная архитектура никоим образом это не запрещает :-)
Но потом, практика показала, что это не максима и вообще хранилище данных это отдельная тема.
Т.к. желательно чтобы микросеврисы были без состояние, то это стоточение где-то должно быть.
Одно из хранилищ состояния может быть, например, БД.
Куда микросервисы обращаются за данными.
Поэтому в публичном АПИ, просто создается микросервис с JOIN-ом, к которому обращаются при надобности :-)
Если нужно делать «джойн» данных нескольких микросервисов на уровне запросов к БД, то это, в общем случае, нарушение принципов MSA.
Т.е. разделения то что не должно меняться (данные) и то что может быть изменено со временем (логика работы с данными).
Почему данные не должны меняться — потому что история. Мы должны знать что у нас было в определенный момент времени.
Поэтому данные не меняются, а добавляются.
Так что общее хранилище данных для микросервисов — это норма.
При чем это может быть не только БД, но например kafka :-)
Проблема общего доступа к данным в БД как раз в том, что из-за требований бизнеса и добавления новых фич схема данных меняется, и если несколько микросервисов работают с одной БД то изменение схемы требует одновременного обновления всех этих микросервисов — т.е. между ними создаётся жёсткая связь на данных. Ну и помимо очевидной проблемы при изменении схемы данных есть ещё проблема что БД — это одна большая глобальная переменная, так что когда разный код начинает по-разному с ней работать то изменения в том, как один микросервис читает/пишет данные в БД внезапно могут повлиять на другой микросервис.
Эта проблема не распространяется на кафку и аналогичные сервисы очередей потому, что данные в них изначально описываются как API — какие у нас топики, какие бывают типы событий, какие по ним даются гарантии, etc. ну и плюс там всё-таки нет свободного RW доступа к этим данным как в обычной БД.
Таким образом, максима про приватную БД у микросервисов это не какая-то глупость времён "зари микросервисного хайпа", а суровая реальность. Конечно, если у Вас данные с БД по своей природе иммутабельные (append-only), плюс формат схемы БД описан и не меняется, то по факту такая БД ничем не отличается от любого другого API — микросервисам доступны только операции которые не могут "сломать" данные в БД (кстати, чтобы это гарантировать, крайне желательно ограничить разрешённые операции для тех аккаунтов, под которыми микросервисы подключаются к БД, исключительно SELECT и INSERT, если речь об SQL) и сохраняется обратная совместимость. В этом случае никакой проблемы чтобы с этой БД работало несколько микросервисов нет. Но, мягко говоря, это не самый типичный сценарий использования БД, поэтому ту "максиму" он никак не отменяет.
При изменении схемы данных, все равно будут меняться микросервисы, которые обращаются к микросервису работающему с БД.
Т.к. контракт скорее всего поменяется.
Если он не поменялся… То зачем изменение :-)
Даже если это сделано в угоду оптимизации.
сделать подобие LEFT JOIN между двумя сервисамиПусть он почитает книгу «NoSQL Distilled. A Brief Guide to the Emerging World of Polyglot Persistence.» by Pramod J. Sadalage, Martin Fowler. Там описано как решаются такие проблемы в распределенных системах, хотя книга и не про микросервисы.
Можно ли было это сделать, оставаясь в микросервисах? Наверное, да. Вопрос только в том, какую логику обновления сервисов задействовать после изменения в общих зависимостях? Автосборка всех зависимых сервисов? Отказ от деплоя или частичный деплой только прошедших тесты, сервисов? И т.д.
Тестирование и развертывание изменений в этих общих библиотеках повлияло на все наши направления. Это начало требовать значительного времени и усилий для поддержки. Внесение изменений для улучшения наших библиотек, зная что нам придется тестировать и разворачивать десятки сервисов, было рискованным предложением.
ИМХО, в случае, такого большого кол-ва микросервисов лучше не иметь общих библиотек вообще (малейшая ошибка в общей библиотеке и лягут все микросервисы сразу).
Или они должны быть сильно ограничены и сигментированы (грубо говоря, на rest клиента одна библиотека, на работу с xml другая, не обязательно в разных репозиториях, но в разных jar'никах или в чем они делают выдачу).
К своим общим библиотекам лучше относится как к любой другой opensource библиотеки — если она не нужна, не включать, если есть opensource библиотека более подходящая — брать ее, а не свою общую библиотеку.
Эта обшая библиотека по сути делала все микросервисы Монолитом, который слегка кастомизируется и копируется в 120 инстансов. В результате логично, что оказалось, что Монолит для реализации Монолита лучше.
По моим наблюдениям мотивация рассказать о неудачном опыте сильно превышает таковую в случае успеха.
Профнепригодные архитекторы, профнепригодные менеджеры, профнепригодные девелоперы. Куда деваются люди, со знанием — загадка.
Про менежеров у меня есть теория: хорошо менеджерить IT-проект может только хороший программист, который потрогал всю подноготную ремесла. Который понял, зачем тесты, понял, что такое рефакторинг, деплой, архитектура. В общем, понял это непростое ремесло. После этого, человек имеет возможность менеджерить такие проекты хорошо. Но если он хороший программист — зачем ему менеджерить? Я думаю, что большинство хороших прогеров не хотят перерастать в менеджеров.
Если же менеджер не был программистом, то программистам придется выпрашивать (в буквальном смысле) время на рефакторинг/тесты/песочницы.
Судя по зарубежной литературе, хороший менеджер — это мировая проблема :) Вон, старик Брукс аж бестселлер написал про пули и… про то, как он развалил проект ОС/360 :)
А вообще я думаю, что хорошего менеджера практически не должно быть заметно. И это его основная цель: команда должна работать эффективно что с ним, что без. Но чтобы этого достич, нужен хороший менеджер, такая фигня :)
Уверен, что есть исключения, представляющие из собой просто отличных менеджеров, которые ничерта не смыслят в низкоуровневых слоях.
Я думаю, что большинство хороших прогеров не хотят перерастать в менеджеров.
Если же менеджер не был программистом, то программистам придется выпрашивать (в буквальном смысле) время на рефакторинг/тесты/песочницы.
Когда программист идет в менеджеры, это не профессиональный рост, это просто человек уперся в пололок в своей области.
Хорошему менеджеру проекта не необходим опыт разработки, он оперирует другими категориями. Это план выпуска фич, список срочных фиксов, зависимости между задачами, возможность замены разработчика на задаче и т.д.
Вы как программист, можете обосновать необходимость юнит-тестов или рефакторинга? Но так, чтобы было понятно, почему это будет полезно проекту?
Например, юнит-тесты конечному продукту не нужны. Но при последующей разработке пригодятся. Это фиксация функционала и другой разработчик (низко квалифицированный или не погруженный в задачу) не сломает бизнес логику выпустив фикс. Это максимально ранее обнаружение ошибок, т.е. экономит время разработчика.
К конечном итоге это выбор руководства проекта: выпустить фичу позже без технического долга или выпустить фичу быстрее и замедлить дальнейшее развитие.
Попробуйте обосновать рефакторинг. Например, отделение в бэкенде слоя доступа к данным (DAL)
На самом деле не надо ничего обосновывать, равно как не надо и доводить до состояния когда на рефакторинг надо выделять неделю.
Юнит-тесты должны писаться одновременно с кодом, просто чтобы разработчик был уверен в том, что написанный им код запускается, и делает ровно то, что ожидает разработчик. Это не требует много времени на начальное написание (максимум — удваивает время начальной реализации фичи, т.е. лишний день на среднюю таску), причём это время с лихвой компенсируется экономией времени на последующих багфиксах и доработках ещё до релиза. Единственное исключение, когда затраты на юнит-тесты могут не успеть компенсироваться к моменту релиза — при разработке прототипа. (Ещё одно исключение — когда релиз должен быть к жёсткому дедлайну, напр. для выставки или инвесторов, и его качество не имеет большого значения если проект в принципе запускается и выглядит рабочим в срок, а срыв сроков категорически недопустим и может привести к потере проекта.) В остальных случаях, если написанный код реально должен работать, а не быть рассадником бесконечных багов, "излишние" затраты времени на юнит-тесты реально сводятся к нулю примерно к первому релизу, а дальше просто экономят кучу времени.
Что касается рефакторинга, то в большинстве случаев он должен выполняться в фоне: если трогаешь какой-то кусок кода в рамках текущей задачи — оставь его после себя в чуть лучшем состоянии, чем он был. Тогда время на рефакторинг размазывается незаметным тонким слоем по всем задачам, становится невидимым для менеджера (не в смысле сокрытия от него этих затрат, а в смысле что затраты настолько незначительные, что перестают иметь значение для планирования и управления), и отпадает необходимость выпрашивать под рефакторинг отдельное время. А когда дело доходит до необходимости серьёзных переделок кода, которые требуют заметного времени, то тут речь не столько о рефакторинге, сколько об изменении архитектуры, и обычно эту необходимость можно обосновать в терминах, понятных менеджменту (а если нельзя, то, скорее всего, реальной необходимости в этих изменениях нет).
Единственная проблема вышеописанного подхода — его желательно применять с первого дня разработки. Но и в ситуации, когда большая часть проекта уже написана можно начать применять этот подход хотя бы для нового кода, это заметно поможет, хотя и не решит проблему целиком, так что выпрашивать отдельное время на рефакторинг и тесты всё-равно придётся.
Изменения архитектуры обычно делаются как раз ради изменения наблюдаемых эффектов, причём наблюдаемых как раз менеджером: появления возможности реализации (нормальной, без диких хаков и тех.долга) каких-то новых фич, увеличения объёма данных поддерживаемого проектом и/или скорости его работы, добавления возможности горизонтального масштабирования, вынесения/изоляции отдельных компонентов чтобы можно было их разработку поручить отдельным командам разработчиков, etc. Все эти причины для серьёзной переработки архитектуры видны и понятны менеджменту, в отличие от рефакторинга, который значительно более низкоуровневый (крайне сложно объяснить среднему менеджеру почему важно тратить время на выбор более подходящего имени для переменной и её переименования).
Никакого развивающегося проекта не бывает без рефакторинга. И, кстати, 90% встретившихся мне менеджеров не понимают, что такое Agile/SCRUM и как он работает.Причины рефакторинга бывают разными. Бывают причины потому что в мастер-бранч мержится некачественный код. Т.е. в команде отсутствует Collaborative Development. В таком случае Agile никак не оправдывает низкое качество кода, и даже, более того, предписывает его не трогать, пока он не мешает и не влияет на экономику разработки. А бывает рефакторинг как элемент реализации Evolutionary Design и YAGNI. Тогда — да, это Agile. Но в таком случае, это не рефакторинг, а Evolutionary Design.
Не понятны два момента:
- Чего плохого в общих библиотеках? Они остаются общими, пока могут такими быть. Требования меняются — меняется и код. И не надо пытаться сохранить его, закостылив в "общей" библиотеке ради общности
- Вы сначала перешли на отдельные очереди с раздельным управлением, испытывая проблемы с отказоустойчивостью, а потом психанули и перешли на centrifuge, опять создав проблемы с отказоустойчовостью?
Если я правильно понял, то проблемы у вас были две — микросервисы ради микросервисов и общие библиотеки ради общих библиотек
Выше VolCh ответил зачем им понадобились общие библиотеки. Но суровая правда в том, что общих библиотек, действительно, быть не должно.
Звучит как бред, да, я знаю. Но причина такого жёсткого требования в том, что "библиотеки проекта" (а речь именно о них) обычно не являются "настоящими" библиотеками — такими, как сторонние библиотеки, которыми без проблем пользуется любой проект. Выше уже упоминали, что проблема в том, что нужно было эти общие библиотеки писать как отдельное опенсорсное решение, и это правда — при таком подходе ими можно было бы пользоваться.
Проблема с общими библиотеками возникает из-за того, что, поскольку это "библиотеки проекта", они не пишутся настолько аккуратно, как опенсорсные, в них нет такого тщательного соблюдения semver и обратной совместимости API, они не являются настолько библиотеками общего назначения и в них проникает бизнес-логика этого проекта, и в следствие всего этого к ним предъявляется нетипичное требование "все микросервисы обязаны использовать последнюю версию этих библиотек". В результате общие библиотеки создают жёсткую связь между всеми микросервисами и появляется точка, в которой можно все микросервисы поломать, точка, обновление которой вызывает лавину перевыкатов всех микросервисов, точка, которая создаёт больше проблем, чем решает. Вот поэтому общих библиотек проекта у микросервисов быть не должно. А общие опенсорсные — без проблем, причём одна и та же библиотека может в разных микросервисах использоваться разной версии.
Вот поэтому общих библиотек проекта у микросервисов быть не должно.
Как минимум, имеют право на жизнь, библиотеки проекта/продукта, инкапсулирующие конкретный способ использование других микросервисов, например икапсулирующие HTTP-проткол.
Либо эти библиотеки получаются достаточно "общего назначения", и тогда проще писать их как опенсорсные и не выдвигать жёсткого требования что все микросервисы обязаны ими пользоваться, либо этот код получается достаточно специфичным для проекта, и в этом случае я предпочитаю держать его в отдельном репо-шаблоне для создания новых микросервисов.
В случае с шаблоном мы получаем возможность быстро создать типичный микросервис с типичными реализациями всякой сетевой фигни вроде работы с реестром сервисов, поддержкой контекста запросов, логирования, etc. — но поскольку после момента создания нового микросервиса он никак не привязан к этому шаблону и все, условно, "общие библиотеки проекта" он получает в виде копии в момент создания из шаблона, то у него нет никаких общих зависимостей с другими микросервисами, что снимает проблему.
Пока использование всеми сервисами этой библиотеки вообще, и только её последней версии в частности, не является обязательным — никаких проблем. Если сервисы предоставляют стабильное и документированное API, а библиотека существует исключительно для удобства разработчиков тех сервисов, которым лень напрямую вызывать REST API — всё в порядке. Как только библиотека начинает использоваться для сокрытия факта нестабильности и/или недокументированности API, что и приводит к требованию её обязательного использования причём только последней версии, вот тогда возникает та проблема, из-за которой "общих библиотек проекта быть не должно".
Цель подобных библиотек как раз уменьшения количества зависимого от API сервиса кода, чтобы при изменении API сервиса достаточно было обновить только библиотеку на клиентах (естественно, если API библиотеки не изменился), а не переписывать код самих клиентов. Ну и в целом она сама может являться документацией к сервису.
Т.е. их использование не должно быть обязательным.
В противном случае — да получаем монолит в библиотеках.
Т.к. он разнесен по разным приложениям :-)
И в каждом конкретном приложении он уникален.
А то что есть похожие куски, которые делают почти одно и то же…
То да их можно вынести в библиотеки.
Но проектирование АПИ, не совсем тривиальная вещь. :-)
Например: имеем кастомный парсер текста, использующийся в 10 ти сервисах. И что — делать 10 копий?
Если у вас получается критической скорость каналов связи для загрузки математического микросервиса, то что-то у вас спроектировано неочеНе загрузки, а работы. Если выносить 2+2 в микросервис, то, боюсь, закончится как в статье.
Тесты же должны быть :-)
Если микросервис работает правильно, то зачем его менять?
Если нашли «ошибку» в одном микросервисе, то не факт, что это же ошибка есть в другом. Не смотря на то что код может быть один и тот же.
2) Могут меняться требования
2) Да могут, но не всегда для всех и сразу микросеврисов.
Т.е. «общий» код который уже не правильно работает в одном микросервисе, в другом микросеврисе наоборот работает правильно.
2) в случае библиотечного кода решается версионированием по-горячему. Там, где ошибка проявилась, ставится новая версия библиотеки с хотфиксом, а остальные её пользователи усилиенно исследуют проявляется ли ошибка со старой и новой версией. В зависимости от результатов или обновляются, или библиотека правится так, чтобы все сервисы работали корректно с последней версией. Если же у нас копипаст, то даже не узнаем, что где-то могут быть ошибки аналогичные тем, которые исправлены в одном сервисе.
Даже метод с одним целочисленным параметром потребует написать 2^64 тестов, что практически невозможно.Этого недостаточно, т.к. у модуля/класса может быть внутреннее состояние. Чтобы протестировать хорошо, нужно подавать на вход все возможные последовательности int-ов длины, соответствующей ожидаемому времени работы программы, в которую будет включен тестируемый код ))
Вышел новый: браузер/операционка/бибиотека/формат/субформат. И часть тестов можно выбрасывать. И так — постоянно.
По собственному почти ежедневному опыту, так сказать.
2) Даже 100% покрытие не гарантирует правильности работы во всех случаях. Банальный пример: 100% покрытия перебора по массиву, но теста на пустой массив нет, в рамках системы «код-тесты» это является неопределенным поведением, о котором нельзя сказать правильно оно в коде или нет. И даже в требованиях такая ситуация может быть не отражена — нужно уточнять требования.
Юнит-тестами — да можно/нужно
Ну, была у нас реализация (int a, int b): int => a +b
, а стала (int a, int b): int => (a * 2 + b * 2) / 2
(допустим это быстрее гораздо). Требования не изменились, но вот области эквивалентности с предыдущей реализацией не совпадают.
И это я ещё не вспоминал про создание ветвлений в модуле под конкретный тест-кейз специально чтобы тест прошёл, например, если сроки горят, а CI не пускает.
Мой вопрос в другом. Вам действительно дают в ТЗ все граничные значения всех параметров?
Например, макс. число символов, которое юзер может вписать в любую строку поиска, минимальное и макс. разрешение экрана у юзера, а то положишь координату окна в int32, а у него монитор 248×250 пикселей :)
«My general rule of thumb: don’t violate DRY within a microservice, but be relaxed about violating DRY across all services. The evils of too much coupling between services are far worse than the problems caused by code duplication. There is one specific use case worth exploring further, though.»Если интересуют подробности этой проблемы, то их полностью раскрывает Sam Newman в «Building Microservices. Designing Fine-Grained Systems». Микросервисы действительно поощряют дублирование кода, но только в тех случаях где это позволяет снизить сопряжение (coupling).
Библиотеки не являются полноценной альтернативой микросервисам — в нормальных библиотеках обычно не должно быть своего логирования, своей БД… но вот встроенные микросервисы, особенно если они stateless, от библиотек отличить действительно довольно сложно.
www.arbinada.com/ru/node/1651
Прощайте, микросервисы: от ста проблемных детей до одной суперзвезды