(С) Альберт Эйнштейн
Статей о микросервисах, их достоинствах и недостатках в последнее время написано немало. Однако как-то редко кто пишет об имплементации микросервисной архитектуры, и прежде всего, именно об микросервисе, как о кирпичике, из которой и строится потом здание такого приложения. Я попытался восполнить этот пробел, и поделиться своим опытом в разработке http-микросервиса, вылившемся в конечном счёте в небольшую библиотеку под не оставляющим места для сомнений названием Microservice. Код написан на прекрасно подходящем для микросервисов, простом и удобном языке программирования Golang.
В последнее время мне довелось поработать над созданием микросервисов на языке Golang. Это был интересный опыт. Однако во многом получается (по крайней мере у меня), что каждый раз новый микросервис создаётся заново и с нуля. Конечно, можно использовать предыдущие наработки, но в таком подходе нет некоей системности, и мне захотелось сделать инструмент, который бы несколько упорядочил такую работу (искренне на это надеюсь). Не сомневаюсь, это не единственная идея в разработке микросервисов, но если вы ими интересуетесь, возможно, вам будет интересно прочитать данную статью.
Что такое микросервис, я думаю, во избежание холивара, тут не стоит говорить. Также и о достоинствах и недостатках промолчим. Отмечу лишь, что они есть, и именно благодаря наличию оных микросервисная архитектура востребована.
Архитектура микросервиса (тут и дальше речь о http микросервисе) в моей библиотеке включает в себя хэндлер, тюнер (конфигуратор), демонстрационный middleware. Работает всё очень просто: в приложении загружается конфигурация с учётом файла конфигурации, окружения и командной строки. Под нужные роуты формируется из middleware и хэндлера обработчик. Далее запускается сервер и по запросу приложение отрабатывает нужные очереди. По сути, это подходит и может быть использовано как прототип обычного http приложения, но пожалуй, для такого приложения я бы использовал немного другую архитектуру.
Тюнер
Важным моментом для микросервиса является загрузка конфигурации. Я постарался сделать этот функционал максимально гибким. По умолчанию конфигурация загружается из указанного файла (`config.toml`). Адрес файла конфигурации можно изменить из командной строки, например так: вашсервис -confile config.toml Таким образом можно создать несколько разных конфигурационных файла и запускать микросервис с одной из конфигураций по выбору.
Поскольку в конфигурировании микросервиса задействован не только файл, но и переменные окружения и параметры командной строки, то уточню порядок и приоритетность конфигурирования. Самым низким приоритетом обладает конфигурация из файла. Если в операционной системе настроены переменные окружения, то они имеют более высокий приоритет, чем переменные из конфигурационного файла. Параметры командной строки имеют самый важный приоритет. Для изменения параметра в командной строке нужно указывать его название в виде названии раздела и названия параметра (с прописной буквы!). Вот пример изменения порта — вашсервис -Main/Port 85
Ещё немного о конфигурировании: помимо варианта с закидыванием конфигурации в заранее заготовленную структуру, вполне можно было бы использовать вариант импорта данных в обычный map, и потом спокойно использовать значения по ключу. У этого способа есть несомненное достоинство — не нужно добавляя данные в конфигурационный файл дублировать это в конфигурационной структуре. Т.е. всё проще и быстрее. Минусом же будет то, что ошибки неправильного указания ключа переносятся с этапа компиляции на этап выполнения, и кроме того, тут IDE уже не станет нам делать подсказки, что кстати, само по себе очень хорошо в плане защиты от опечаток и как следствие — ошибок.
Middleware
Чтобы не засорять пространство хэндлеров, в микросервисе предусмотрено использование сервисного функционала в стиле middleware (однако не совсем в классическом понимании гоферного миддлваре). К каждому такому сервису предъявляются минимальные требования: он должен принимать в качестве аргумента функцию, принимающую http.ResponseWriter, *http.Request и возвращать такую же функцию. В дистрибутиве продемонстрирован пример с подключением метрики для фиксации времени обработки запроса (duration).
В зависимости от того, какие задачи решаются микросервисом и в каком окружении он это делает, почти наверняка вам потребуются и другие сервисы, например валидация. Рассмотрите для них возможность подключения к хэндлеру в качестве middleware. И обратите внимание, что демонстрационная метрика вынесена в отдельный подкаталог, чтобы подчеркнуть дистанцию между микросервисом и используемыми в нём middleware.
Handler
В дистрибутиве handler содержит в себе совсем мало кода. Однако именно его методы и являются теми хэндлерами, которые обработают поступивший запрос, всё остальное по сути обвес микросервиса. Если это так, то почему бы не сделать просто кучу автономных функций, которые и будет вызывать роутер? На это есть причина: благодаря объединению посредством структуры хэндлеры-методы теперь смогут иметь доступ (если потребуется) к полям структуры (общему контексту приложения). На самом деле очень удобно для каждого публичного метода (читай — обработчика) создать отдельный файл, например handle_hello_word.go, что впрочем не мешает организовать всё каким угодно другим образом.
Создание нового хэндлера
Для этого нужно просто создать новый публичный метод в handler, который на вход принимает http.ResponseWriter, *http.Request. Посмотрите созданный для демонстрации метод HelloWorld.
Perfomance
Для общего понимания того, что скорость работы микросервиса при использовании предложенной архитектуры будет достаточно высокой, приведу результаты бенчмарка, полученные мной на моём компьютере:
- BenchmarkMain-2 10000000 195 ns/op
- BenchmarkMainParallel-2 10000000 107 ns/op
Зависимости
- Логер github.com/Sirupsen/logrus
- Роутер github.com/claygod/Bxog
- Конфиг github.com/BurntSushi/toml
Любую из перечисленных библиотек можно заменить или дополнить, в данном случае они скорее призваны показать, в какую сторону развивать свой микросервис. Возможно, вам также полезно будет подключить логстеш и инфлюкс.
Заключение
Библиотека Microservice не претендует на лавры единственно верного решения, но при случае, надеюсь, сможет помочь вам сформировать архитектуру собственного http-микросервиса, став прототипом будущего приложения.
Ссылки
- Библиотека github.com/claygod/microservice
- Документация godoc.org/github.com/claygod/temp/microservice-doc
- Report goreportcard.com/report/github.com/claygod/microservice
UPD
С учётом комментариев немного изменил библиотечку, удалив хранилище и подправив метод организации мидлваре (без очередей), что кстати, даже немного ускорило работу приложения в бенче.
Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
Комментарии (25)
mgremlin
29.12.2016 19:28+2Это клево. Наверное. Если б хоть чего-нибудь понял — обязательно был бы более уверен!
Автор! Нет бы хоть в общих чертах объяснить, для проходящих мимо. И примерчиков бы приложил пару штук! Не тут, так хоть в доках или на гитхабе.
Иначе в массы не пойдет!
ЗЫ. А вот роутер интересный, попробую на досуге, thanks.
afanasiy_nikitin
30.12.2016 02:39+1Статья слишком сумбурна, чтобы применять ее в продакшене
zelenin
30.12.2016 03:09+1да и вообще «микросервис» здесь только в названии статьи. В самом коде нет ничего, что бы отличало его от простой заготовки веб-приложения.
claygod
30.12.2016 09:04-1Микросервис и должен быть прост, или Вы считаете иначе?
Сам микросервис можно организовать как угодно, лишь бы давал правильные ответы, а я всего лишь предлагаю вариант. И если у Вас есть большой опыт создания микросервисов, то думаю, Вам не составит труда привести примеры и своё видение внутреннего строения микросервиса?zelenin
30.12.2016 10:07+1Микросервис и должен быть прост, или Вы считаете иначе?
изначально он должен обладать единичной зоной ответственности
Вам не составит труда привести примеры и своё видение внутреннего строения микросервиса
я говорю, что в вашем «микросервисе» нет ничего, чтобы отличало его от любой заготовки любого веб-приложения. И я против, чтобы все, что создается на go и смотрящим в веб автоматически называлось микросервисом. Строго говоря, микросервис может быть веб-приложением, но может и не быть. Так же справедливо и обратное: не каждое веб-приложение — микросервис. Соответственно и создали вы болванку того, что может быть микросервисом, а может и не быть — каких-то отличительных особенностей в коде нет.
Добавьте сервис дискавери, сервисы по работе с апи/апи-шлюзом, балансировку, (g)rpc-клиент/сервер.
Фреймворки для создания микросервиса на го уже существуют — можете ознакомиться с фичами.claygod
30.12.2016 10:17-1Добавьте сервис дискавери, сервисы по работе с апи/апи-шлюзом, балансировку, (g)rpc-клиент/сервер.
Вы описываете микросервисную архитектуру, а не микросервис.zelenin
30.12.2016 10:19нет, я описываю фичи, которыми может и часто обладает единичный микросервис.
claygod
30.12.2016 10:28zelenin, если у вас всё это содержится в микросервисе, то он сам фактически содержит микросервисы. Обратите внимание, Вы сами пишете слово `сервисы` во множественном числе.
zelenin
30.12.2016 10:43я использую слово «сервисы» в другом контексте. Апи-клиент или клиент для сервис-дискавери являются сервисами приложения.
claygod
30.12.2016 10:52Если сравнивать Ваше и моё видение микросервиса, то похоже, что Вы делаете микросервисы более «жирными». В принципе, если их ответственность при этом мала и неделима, это тоже вполне рабочий вариант. Мне кажется, я понимаю Вашу точку зрения. Если не затруднит, поглядите сделанный мной для фана ММОА.
zelenin
30.12.2016 11:02+1все не так. я лишь сказал, что вы сделали не заготовку под микросервис, а заготовку под веб-приложение. Оно может быть как приложением, отдающим данные по api, так и блогом. Но вы почему-то назвали это модным словом «микросервис», хотя для такой более узкой специализации у болванки нет никаких специализированных фич (не факт что они все и нужны в конкретном микросервисе).
Обсуждение исчерпало себя.
umputun
04.01.2017 10:10+3Удивительно непонятное описание продукта, просто исключительно странное. Даже мое понимание области в которой автор копает, не вносит ясности. Термины используется так, как я нигде не видел. Видимо очередь это не очередь а конвейер? И что за storage, для чего, куда и почему его основное достоинство это авто–дополнение в IDE? Хотя, похоже и storage это не то, что все под этим понимают, т.к. в нем автор предлагает хранить (?) middleware про которое я тоже уже не уверен, что это такое у автора и зачем его там хранить.
Пошел на гитхабе посмотреть, думал там возникнет просветление, но на глаза попалось README с очень похожим на статью текстом, но на иностранном языке, который немного похож на английский, но точно не он.
Я не для придирок или прочих издевательств, но в попытке понять о чем это было вообще и как оно относится к микросервисам в частности.
claygod
04.01.2017 11:58Видимо очередь это не очередь а конвейер?
Наверно для меня эти термины близки. Какую Вы принципиальную разницу вкладываете в эти два термина?
middleware про которое я тоже уже не уверен, что это такое у автора и зачем его там хранить
В моём понимании это промежуточное программное обеспечение. В библиотеке для примера это метрики, которые находятся «снаружи» микросервиса.
И что за storage, для чего...
Это простое хранилище объектов, оно только для удобства, чтобы инициализировать миддлваре объекты в одном месте.
авто–дополнение в IDE
Ну… это просто удобно, по крайней мере мне.
но на иностранном языке, который немного похож на английский, но точно не он.
Тут не в бровь, а в глаз, ибо учил я немецкий вперемежку с французским, так что транслятор, это наше всё… :(
umputun
04.01.2017 12:12в очередь я вкладываю понимание, что это queue куда кладут, в общем случае, данные. Кладут и достают последовательно. Конечно, очередь можно использовать мнoго для чего разного, например RPC, но насколько я понимаю, в статье не об этом речь. Конвейер, в моем понимании, это последовательность функциональных шагов для обработки неких данных.
Для термина middleware в го мире есть довольно четкое соглашение, что это такое, и что люди под этим подразумевают, особенно в контексте http обработок. Например вот это https://github.com/pressly/chi/tree/master/middleware
по поводу транслятора — по моему мнению лучше это поменять на оригинал до прогона через перводчик. То, что там читать трудно, а понять — невозможно.
claygod
04.01.2017 12:19Конвейер, в моем понимании, это последовательность функциональных шагов для обработки неких данных.
Спасибо, я подумаю над этим, может быть изменю названия, и тогда естественно, поправлю и в статье.
Для термина middleware в го мире есть довольно четкое соглашение
Если Вам не трудно, сформулируйте здесь (не только для меня), и тогда уж… то, что я называю миддлваре, как в принципе это назвали бы Вы?
То, что там читать трудно, а понять — невозможно
Скорей всего сокращу до простых предложений, которые и транслятор сможет осилить (надеюсь).umputun
04.01.2017 21:09+2насколько я вижу, у тебя это тоже типа middlware, но с нестандартной сигнатурой func(http.ResponseWriter, *http.Request) (http.ResponseWriter, *http.Request). Если поменять на func(h http.Handler) http.Handler то станет как у всех, и самое главное — ты сможешь использовать все подобные, готовые middlware без всяких модификаций.
Общее замечание: в принципе, идея создания фреймоврка такого рода мнe кажется делом сомнительным. Во первых, есть неплохие и проверенные боем библиотеки, которые это уже делают основнию часть, например chi. Во вторых — идея добавления туда и конфигурации и еще чего мне не кажется полезной. Да, я понимаю, что при создании сервиса хочется упростить начальные телодвижения, однако лично я пошел по другому пути и написал простой кодо-генератор, который делает рабочий скелет приложения с chi, набором middlware для него (у меня в каждом сервисе надо определенный набор, например JWT), логгером, main с флагами и env (github.com/jessevdk/go-flag) перехватчик сигналов и еще по мелочи. Кроме того, оно генерит не только исходный текст, но и все, что надо для сборки и деплоя (Dockerfile, CI yml, ansible yml)ninedraft
05.01.2017 13:52А планируете ли вы выкладывать этот инструмент на гитхаб?
umputun
05.01.2017 18:34назвать это гордо «интрументом» я бы не решиился. Там весь код это 50-60 строк определяющие структуру параметров, заворачивающие «templates» со всем, что человек туда положил в bindata и делающее по всем файлам в нем filepath.Walk с ExecuteTemplate. Могу, конечно, выложить, просто мне казалось это тривиальным.
claygod
05.01.2017 15:54С учётом критики в комментах подправил библиотечку, в результате чего очереди и хранилище отвалились за ненадобностью, хотя сами мидлваре я всё же прикручиваю немного по другому.
написал простой кодо-генератор, который делает рабочий скелет приложения
Как-то не приходилось сталкиваться с кодогенерацией, насколько удобным Вам показался этот инструмент?
antonksa
Look at my horse, my horse is amazing! :D