[Go, Микросервисы] Go-swagger как основа взаимодействия микросервисов
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
Здравствуй, NickName! Если ты программист и работаешь с микросервисной архитектурой, то представь, что тебе нужно настроить взаимодействие твоего сервиса А с каким-то новым и ещё неизвестным тебе сервисом Б. Что ты будешь делать в первую очередь?
Если задать такой вопрос 100 программистам из разных компаний, скорее всего, мы получим 100 разных ответов. Кто-то описывает контракты в swagger, кто-то в gRPC просто делает клиенты к своим сервисам без описания контракта. А кто-то и вовсе хранит JSON в гуглодоке :D. В большинстве компаний складывается свой подход к межсервисному взаимодействию на основании каких-либо исторических факторов, компетенций, стека технологий и прочего. Я хочу рассказать, как сервисы в Delivery Club общаются друг с другом и почему мы сделали именно такой выбор. И главное — как мы обеспечиваем актуальность документации с течением времени. Будет много кода!
Ещё раз привет! Меня зовут Сергей Попов, я тим-лид команды, отвечающей за поисковую выдачу ресторанов в приложениях и на сайте Delivery Club, а также активный участник нашей внутренней гильдии разработки на Go (возможно, мы об этом ещё расскажем, но не сейчас).
Сразу оговорюсь, речь пойдет, в основном, про сервисы, написанные на Go. Генерирование кода для PHP-сервисов мы ещё не реализовали, хотя достигаем там единообразия в подходах другим способом.
К чему, в итоге, мы хотели прийти:
- Обеспечить актуальность контрактов сервисов. Это должно ускорить внедрение новых сервисов и упростить коммуникацию между командами.
- Прийти к единому способу взаимодействия по HTTP между сервисами (пока не будем рассматривать взаимодействия через очереди и event streaming).
- Стандартизировать подход к работе с контрактами сервисов.
- Использовать единое хранилище контрактов, чтобы не искать доки по всяким конфлюенсам.
- В идеале, генерировать клиенты под разные платформы.
Из всего перечисленного на ум приходит Protobuf как единый способ описания контрактов. Он имеет хороший инструментарий и может генерировать клиенты под разные платформы (наш п.5). Но есть и явные недостатки: для многих gRPC остается чем-то новым и неизведанным, а это сильно усложнило бы его внедрение. Ещё одним важным фактором было то, что в компании давно принят подход «specification first», и документация уже существовала на все сервисы в виде swagger или RAML-описания.
Go-swagger
Так совпало, что в то же время мы начали адаптацию Go в компании. Поэтому следующим нашим кандидатом на рассмотрение оказался go-swagger — инструмент, который позволяет генерировать клиентов и серверный код из swagger-спецификации. Из очевидных недостатков — он генерирует код только для Go. На самом деле, там используется гошное кодогенерирование, и go-swagger позволяет гибко работать с шаблонам, так что, теоретически, его можно использовать для генерирования кода на PHP, но мы ещё не пробовали.
Go-swagger — это не только про генерирование транспортного слоя. Фактически он генерирует каркас приложения, и тут я бы хотел немного упомянуть о культуре разработки в DC. У нас есть Inner Source, а это значит, что любой разработчик из любой команды может создать pull request в любой сервис, который у нас есть. Чтобы такая схема работала, мы стараемся стандартизировать подходы в разработке: используем общую терминологию, единый подход к логированию, метрикам, работе с зависимостями и, конечно же, к структуре проекта.
Таким образом, внедряя go-swagger, мы вводим стандарт разработки наших сервисов на Go. Это еще один шаг навстречу нашим целям, на который мы изначально не рассчитывали, но который важен для разработки в целом.
Первые шаги
Итак, go-swagger оказался интересным кандидатом, который, кажется, может покрыть большинство наших хотелок требований.
Примечание: весь дальнейший код актуален для версии 0.24.0, инструкцию по установке можно посмотреть в нашем репозитории с примерами, а на официальном сайте есть инструкция по установке актуальной версии.
Давайте посмотрим, что он умеет. Возьмём swagger-спеку и сгенерируем сервис:
> goswagger generate server \
--with-context -f ./swagger-api/swagger.yml \
--name example1
Получилось у нас следующее:
Makefile и go.mod я уже сделал сам.
Фактически у нас получился сервис, который обрабатывает запросы, описанные в swagger.
> go run cmd/example1-server/main.go
2020/02/17 11:04:24 Serving example service at http://127.0.0.1:54586
> curl http://localhost:54586/hello -i
HTTP/1.1 501 Not Implemented
Content-Type: application/json
Date: Sat, 15 Feb 2020 18:14:59 GMT
Content-Length: 58
Connection: close
"operation hello HelloWorld has not yet been implemented"
Второй шаг. Разбираемся с шаблонизацией
Очевидно, что сгенерированный нами код далёк от того, что мы хотим видеть в эксплуатации.
Что мы хотим от структуры нашего приложения:
- Уметь конфигурировать приложение: передавать настройки подключения к БД, указывать порт HTTP-соединений и прочее.
- Выделить объект приложения, который будет хранить состояние приложения, подключение к БД и прочее.
- Сделать хэндлеры функциями нашего приложения, это должно упростить работу с кодом.
- Инициализировать зависимости в main-файле (в нашем примере этого не будет, но мы всё равно этого хотим.
Для решения новых задач мы можем переопределить некоторые шаблоны. Для этого опишем следующие файлы, как это сделал я (Github):
Нам необходимо описать файлы шаблонов (`*.gotmpl`) и файл для конфигурации (`*.yml`) генерирования нашего сервиса.
Далее по порядку разберем те шаблоны, которые сделал я. Глубоко погружаться в работу с ними не буду, потому что документация go-swagger достаточно подробная, например, вот описание файла конфигурации. Отмечу только, что используется Go-шаблонизация, и если у вас уже есть в этом опыт или приходилось описывать HELM-конфигурации, то разобраться не составит труда.
Конфигурирование приложения
config.gotmpl содержит простую структуру с одним параметром — портом, который будет слушать приложение для входящих HTTP-запросов. Также я сделал функцию InitConfig, которая будет считывать переменные окружения и заполнять эту структуру. Вызывать буду из main.go, поэтому InitConfig сделал публичной функцией.
package config
import (
"github.com/pkg/errors"
"github.com/vrischmann/envconfig"
)
// Config struct
type Config struct {
HTTPBindPort int `envconfig:"default=8001"`
}
// InitConfig func
func InitConfig(prefix string) (*Config, error) {
config := &Config{}
if err := envconfig.InitWithPrefix(config, prefix); err != nil {
return nil, errors.Wrap(err, "init config failed")
}
return config, nil
}
Чтобы этот шаблон использовался при генерировании кода, его нужно указать в YML-конфиге:
layout:
application:
- name: cfgPackage
source: serverConfig
target: "./internal/config/"
file_name: "config.go"
skip_exists: false
Немного расскажу про параметры:
- name — несёт чисто информативную функцию и на генерирование не влияет.
- source — фактически путь до файла шаблона в camelCase, т.е. serverConfig равносильно ./server/config.gotmpl.
- target — директория, куда будет сохранен сгенерированный код. Здесь можно использовать шаблонизацию для динамического формирования пути (пример).
- file_name — название сгенерированного файла, здесь также можно использовать шаблонизацию.
- skip_exists — признак того, что файл будет сгенерирован только один раз и не будет перезаписывать существующий. Для нас это важно, потому что файл конфига будет меняться по мере роста приложения и не должен зависеть от генерируемого кода.
В конфиге кодогенерирования нужно указывать все файлы, а не только те, которые мы хотим переопределить. Для файлов, которые мы не меняем, в значении source указываем asset:<путь до шаблона>, например, как здесь: asset:serverConfigureapi. Кстати, если интересно посмотреть оригинальные шаблоны, то они здесь.
Объект приложения и хэндлеры
Объект приложения для хранения состояния, подключений БД и прочего я описывать не буду, всё аналогично только что сделанному конфигу. А вот с хэндлерами всё немного интереснее. Наша ключевая цель состоит в том, чтобы при добавлении URL в спецификацию у нас в отдельном файле создалась функция с заглушкой, и самое главное, чтобы наш сервер вызывал эту функцию для обработки запроса.
Опишем шаблон функции и заглушки:
package app
import (
api{{ pascalize .Package }} "{{.GenCommon.TargetImportPath}}/{{ .RootPackage }}/operations/{{ .Package }}"
"github.com/go-openapi/runtime/middleware"
)
func (srv *Service){{ pascalize .Name }}Handler(params api{{ pascalize .Package }}.{{ pascalize .Name }}Params{{ if .Authorized }}, principal api{{ .Package }}.{{ if not ( eq .Principal "interface{}" ) }}*{{ end }}{{ .Principal }}{{ end }}) middleware.Responder {
return middleware.NotImplemented("operation {{ .Package }} {{ pascalize .Name }} has not yet been implemented")
}
Немного разберём пример:
- pascalize — приводит строку с CamelCase (описание остальных функции здесь).
- .RootPackage — пакет сгенерированного веб-сервера.
- .Package — название пакета в сгенерированном коде, в котором описаны все необходимые структуры для HTTP-запросов и ответов, т.е. структуры. Например, структура для тела запроса или структура ответа.
- .Name — название хэндлера. Оно берётся из operationID в спецификации, если указано. Я рекомендую всегда указывать operationID для более очевидного результата.
Конфиг для хэндлера следующий:
layout:
operations:
- name: handlerFns
source: serverHandler
target: "./internal/app"
file_name: "{{ (snakize (pascalize .Name)) }}.go"
skip_exists: true
Как видите, код хэндлеров не будет перезаписываться (skip_exists: true), а название файла будет генерироваться из названия хэндлера.
Окей, функция с заглушкой есть, но веб-сервер ещё не знает, что эти функции нужно использовать для обработки запросов. Я исправил это в main.go (весь код приводить не буду, полную версию можно найти здесь):
package main
{{ $name := .Name }}
{{ $operations := .Operations }}
import (
"fmt"
"log"
"github.com/delivery-club/go-swagger-example/{{ dasherize .Name }}/internal/generated/restapi"
"github.com/delivery-club/go-swagger-example/{{ dasherize .Name }}/internal/generated/restapi/operations"
{{range $index, $op := .Operations}}
{{ $found := false }}
{{ range $i, $sop := $operations }}
{{ if and (gt $i $index ) (eq $op.Package $sop.Package)}}
{{ $found = true }}
{{end}}
{{end}}
{{ if not $found }}
api{{ pascalize $op.Package }} "{{$op.GenCommon.TargetImportPath}}/{{ $op.RootPackage }}/operations/{{ $op.Package }}"
{{end}}
{{end}}
"github.com/go-openapi/loads"
"github.com/vrischmann/envconfig"
"github.com/delivery-club/go-swagger-example/{{ dasherize .Name }}/internal/app"
)
func main() {
...
api := operations.New{{ pascalize .Name }}API(swaggerSpec)
{{range .Operations}}
api.{{ pascalize .Package }}{{ pascalize .Name }}Handler = api{{ pascalize .Package }}.{{ pascalize .Name }}HandlerFunc(srv.{{ pascalize .Name }}Handler)
{{- end}}
...
}
Код в импорте выглядит сложным, хотя на самом деле здесь просто Go-шаблонизация и структуры из репозитория go-swagger. А в функции main мы просто присваиваем хэндлерам наши сгенерированные функции.
Осталось сгенерировать код с указанием нашей конфигурации:
> goswagger generate server \
-f ./swagger-api/swagger.yml \
-t ./internal/generated -C ./swagger-templates/default-server.yml \
--template-dir ./swagger-templates/templates \
--name example2
Финальный результат можно посмотреть в нашем репозитории.
Что мы получили:
- Мы можем использовать свои структуры для приложения, конфигов и всего, что захотим. Самое главное — это достаточно просто встраивается в генерируемый код.
- Мы можем гибко управлять структурой проекта, вплоть до названий отдельных файлов.
- Go-шаблонизация выглядит сложной и к ней нужно привыкнуть, но в целом это очень мощный инструмент.
Третий шаг. Генерирование клиентов
Go-swagger позволяет генерировать и пакет клиента для нашего сервиса, который могут использовать другие Go-сервисы. Здесь я не буду подробно останавливаться на генерировании кода, подход точно такой же, как и при генерировании серверного кода.
Для проектов на Go принято складывать публичные пакеты в ./pkg, мы сделаем так же: положим клиент для нашего сервиса в pkg, а сам код сгенерируем следующим образом:
> goswagger generate client -f ./swagger-api/swagger.yml -t ./pkg/example3
Пример сгенерированного кода здесь.
Теперь все потребители нашего сервиса могут импортировать себе этот клиент, например, по тэгу (для моего примера тэг будет example3/pkg/example3/v0.0.1).
Шаблоны клиентов можно настраивать, чтобы, например, прокидывать open tracing id из контекста в заголовок.
Выводы
Естественно, наша внутренняя реализация отличается от приведенного здесь кода, в основном, за счёт использования внутренних пакетов и подходов к CI (запуск различных тестов и линтеров). В сгенерированном коде из коробки настроен сбор технических метрик, работа с конфигами и логирование. Мы стандартизировали все общие инструменты. За счёт этого мы упростили разработку в целом и выпуск новых сервисов в частности, обеспечили более быстрое прохождение чек-листа сервиса перед деплоем на прод.
Давайте проверим, получилось ли достигнуть первоначальных целей:
- Обеспечить актуальность описанных для сервисов контрактов, это должно ускорить внедрение новых сервисов и упростить коммуникацию между командами — Да.
- Прийти к единому способу взаимодействия по HTTP между сервисами (пока не будем рассматривать взаимодействия через очереди и event streaming) — Да.
- Стандартизировать подход к работе с контрактами сервисов, т.к. мы давно пришли к подходу Inner Source в разработке сервисов — Да.
- Использовать единое хранилище контрактов, чтобы не искать документацию по всяким конфлюенсам — Да (фактически — Bitbucket).
- В идеале, генерировать клиенты под разные платформы — Нет (на самом деле, не пробовали, шаблонизация не ограничивает в этом плане).
- Внедрить стандартную структуру сервиса на Go — Да (дополнительный результат).
Внимательный читатель, наверное, уже задался вопросом: как файлы шаблонов попадают в наш проект? Сейчас мы храним их в каждом нашем проекте. Это упрощает повседневную работу, позволяет что-то настраивать под конкретный проект. Но есть и другая сторона медали: отсутствует механизм централизованного обновления шаблонов и доставки новых фич, в основном, связанных с CI.
P.S. Если этот материал понравится, то в дальнейшем подготовим статью про стандартную архитектуру наших сервисов, расскажем, какими принципами мы пользуемся при разработке сервисов на Go.
===========
Источник:
habr.com
===========
Похожие новости:
- [Разработка мобильных приложений, Разработка под Android, Монетизация мобильных приложений] Как внедрить in-app подписки в Android-приложения: советы и рекомендации
- [Open source, .NET, Микросервисы] Как мы разрабатывали кроссплатформенную BPMS
- [Научно-популярное, Космонавтика] Что помешало экипажу Crew Dragon выйти из корабля?
- [.NET, C#, Математика] Тензоры для C#. И матрицы, и векторы, и кастомный тип, и сравнительно быстро
- [Python, Программирование] Пишем веб сервис на Python с помощью FastAPI
- [Научно-популярное, Космонавтика] Событие года. 2020: успешное возвращение пилотируемого корабля Crew Dragon Endeavour в миссии Demo-2
- [Браузеры, Контекстная реклама, IT-компании] Google выпустила расширение для браузера Chrome, которое показывает информацию по рекламе на просматриваемых страницах
- [Информационная безопасность, Разработка веб-сайтов, ВКонтакте API, Конференции, Дизайн игр] 27 августа приглашаем на онлайн-митап Hot Frontend
- [Анализ и проектирование систем, Google Cloud Platform] Документирование архитектуры: введение (перевод)
- [API, Asterisk, Lua] Asterisk — это болид «Формулы-1», а не рейсовый автобус
Теги для поиска: #_go, #_mikroservisy (Микросервисы), #_go, #_golang, #_swagger, #_delivery_club, #_dctech, #_microservices, #_api, #_blog_kompanii_mail.ru_group (
Блог компании Mail.ru Group
), #_blog_kompanii_delivery_club_tech (
Блог компании Delivery Club Tech
), #_go, #_mikroservisy (
Микросервисы
)
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 23-Ноя 00:01
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
Здравствуй, NickName! Если ты программист и работаешь с микросервисной архитектурой, то представь, что тебе нужно настроить взаимодействие твоего сервиса А с каким-то новым и ещё неизвестным тебе сервисом Б. Что ты будешь делать в первую очередь? Если задать такой вопрос 100 программистам из разных компаний, скорее всего, мы получим 100 разных ответов. Кто-то описывает контракты в swagger, кто-то в gRPC просто делает клиенты к своим сервисам без описания контракта. А кто-то и вовсе хранит JSON в гуглодоке :D. В большинстве компаний складывается свой подход к межсервисному взаимодействию на основании каких-либо исторических факторов, компетенций, стека технологий и прочего. Я хочу рассказать, как сервисы в Delivery Club общаются друг с другом и почему мы сделали именно такой выбор. И главное — как мы обеспечиваем актуальность документации с течением времени. Будет много кода! Ещё раз привет! Меня зовут Сергей Попов, я тим-лид команды, отвечающей за поисковую выдачу ресторанов в приложениях и на сайте Delivery Club, а также активный участник нашей внутренней гильдии разработки на Go (возможно, мы об этом ещё расскажем, но не сейчас). Сразу оговорюсь, речь пойдет, в основном, про сервисы, написанные на Go. Генерирование кода для PHP-сервисов мы ещё не реализовали, хотя достигаем там единообразия в подходах другим способом. К чему, в итоге, мы хотели прийти:
Из всего перечисленного на ум приходит Protobuf как единый способ описания контрактов. Он имеет хороший инструментарий и может генерировать клиенты под разные платформы (наш п.5). Но есть и явные недостатки: для многих gRPC остается чем-то новым и неизведанным, а это сильно усложнило бы его внедрение. Ещё одним важным фактором было то, что в компании давно принят подход «specification first», и документация уже существовала на все сервисы в виде swagger или RAML-описания. Go-swagger Так совпало, что в то же время мы начали адаптацию Go в компании. Поэтому следующим нашим кандидатом на рассмотрение оказался go-swagger — инструмент, который позволяет генерировать клиентов и серверный код из swagger-спецификации. Из очевидных недостатков — он генерирует код только для Go. На самом деле, там используется гошное кодогенерирование, и go-swagger позволяет гибко работать с шаблонам, так что, теоретически, его можно использовать для генерирования кода на PHP, но мы ещё не пробовали. Go-swagger — это не только про генерирование транспортного слоя. Фактически он генерирует каркас приложения, и тут я бы хотел немного упомянуть о культуре разработки в DC. У нас есть Inner Source, а это значит, что любой разработчик из любой команды может создать pull request в любой сервис, который у нас есть. Чтобы такая схема работала, мы стараемся стандартизировать подходы в разработке: используем общую терминологию, единый подход к логированию, метрикам, работе с зависимостями и, конечно же, к структуре проекта. Таким образом, внедряя go-swagger, мы вводим стандарт разработки наших сервисов на Go. Это еще один шаг навстречу нашим целям, на который мы изначально не рассчитывали, но который важен для разработки в целом. Первые шаги Итак, go-swagger оказался интересным кандидатом, который, кажется, может покрыть большинство наших хотелок требований. Примечание: весь дальнейший код актуален для версии 0.24.0, инструкцию по установке можно посмотреть в нашем репозитории с примерами, а на официальном сайте есть инструкция по установке актуальной версии.
> goswagger generate server \
--with-context -f ./swagger-api/swagger.yml \ --name example1 Получилось у нас следующее: Makefile и go.mod я уже сделал сам. Фактически у нас получился сервис, который обрабатывает запросы, описанные в swagger. > go run cmd/example1-server/main.go
2020/02/17 11:04:24 Serving example service at http://127.0.0.1:54586 > curl http://localhost:54586/hello -i HTTP/1.1 501 Not Implemented Content-Type: application/json Date: Sat, 15 Feb 2020 18:14:59 GMT Content-Length: 58 Connection: close "operation hello HelloWorld has not yet been implemented" Второй шаг. Разбираемся с шаблонизацией Очевидно, что сгенерированный нами код далёк от того, что мы хотим видеть в эксплуатации. Что мы хотим от структуры нашего приложения:
Для решения новых задач мы можем переопределить некоторые шаблоны. Для этого опишем следующие файлы, как это сделал я (Github): Нам необходимо описать файлы шаблонов (`*.gotmpl`) и файл для конфигурации (`*.yml`) генерирования нашего сервиса. Далее по порядку разберем те шаблоны, которые сделал я. Глубоко погружаться в работу с ними не буду, потому что документация go-swagger достаточно подробная, например, вот описание файла конфигурации. Отмечу только, что используется Go-шаблонизация, и если у вас уже есть в этом опыт или приходилось описывать HELM-конфигурации, то разобраться не составит труда. Конфигурирование приложения config.gotmpl содержит простую структуру с одним параметром — портом, который будет слушать приложение для входящих HTTP-запросов. Также я сделал функцию InitConfig, которая будет считывать переменные окружения и заполнять эту структуру. Вызывать буду из main.go, поэтому InitConfig сделал публичной функцией. package config
import ( "github.com/pkg/errors" "github.com/vrischmann/envconfig" ) // Config struct type Config struct { HTTPBindPort int `envconfig:"default=8001"` } // InitConfig func func InitConfig(prefix string) (*Config, error) { config := &Config{} if err := envconfig.InitWithPrefix(config, prefix); err != nil { return nil, errors.Wrap(err, "init config failed") } return config, nil } Чтобы этот шаблон использовался при генерировании кода, его нужно указать в YML-конфиге: layout:
application: - name: cfgPackage source: serverConfig target: "./internal/config/" file_name: "config.go" skip_exists: false Немного расскажу про параметры:
В конфиге кодогенерирования нужно указывать все файлы, а не только те, которые мы хотим переопределить. Для файлов, которые мы не меняем, в значении source указываем asset:<путь до шаблона>, например, как здесь: asset:serverConfigureapi. Кстати, если интересно посмотреть оригинальные шаблоны, то они здесь. Объект приложения и хэндлеры Объект приложения для хранения состояния, подключений БД и прочего я описывать не буду, всё аналогично только что сделанному конфигу. А вот с хэндлерами всё немного интереснее. Наша ключевая цель состоит в том, чтобы при добавлении URL в спецификацию у нас в отдельном файле создалась функция с заглушкой, и самое главное, чтобы наш сервер вызывал эту функцию для обработки запроса. Опишем шаблон функции и заглушки: package app
import ( api{{ pascalize .Package }} "{{.GenCommon.TargetImportPath}}/{{ .RootPackage }}/operations/{{ .Package }}" "github.com/go-openapi/runtime/middleware" ) func (srv *Service){{ pascalize .Name }}Handler(params api{{ pascalize .Package }}.{{ pascalize .Name }}Params{{ if .Authorized }}, principal api{{ .Package }}.{{ if not ( eq .Principal "interface{}" ) }}*{{ end }}{{ .Principal }}{{ end }}) middleware.Responder { return middleware.NotImplemented("operation {{ .Package }} {{ pascalize .Name }} has not yet been implemented") } Немного разберём пример:
Конфиг для хэндлера следующий: layout:
operations: - name: handlerFns source: serverHandler target: "./internal/app" file_name: "{{ (snakize (pascalize .Name)) }}.go" skip_exists: true Как видите, код хэндлеров не будет перезаписываться (skip_exists: true), а название файла будет генерироваться из названия хэндлера. Окей, функция с заглушкой есть, но веб-сервер ещё не знает, что эти функции нужно использовать для обработки запросов. Я исправил это в main.go (весь код приводить не буду, полную версию можно найти здесь): package main
{{ $name := .Name }} {{ $operations := .Operations }} import ( "fmt" "log" "github.com/delivery-club/go-swagger-example/{{ dasherize .Name }}/internal/generated/restapi" "github.com/delivery-club/go-swagger-example/{{ dasherize .Name }}/internal/generated/restapi/operations" {{range $index, $op := .Operations}} {{ $found := false }} {{ range $i, $sop := $operations }} {{ if and (gt $i $index ) (eq $op.Package $sop.Package)}} {{ $found = true }} {{end}} {{end}} {{ if not $found }} api{{ pascalize $op.Package }} "{{$op.GenCommon.TargetImportPath}}/{{ $op.RootPackage }}/operations/{{ $op.Package }}" {{end}} {{end}} "github.com/go-openapi/loads" "github.com/vrischmann/envconfig" "github.com/delivery-club/go-swagger-example/{{ dasherize .Name }}/internal/app" ) func main() { ... api := operations.New{{ pascalize .Name }}API(swaggerSpec) {{range .Operations}} api.{{ pascalize .Package }}{{ pascalize .Name }}Handler = api{{ pascalize .Package }}.{{ pascalize .Name }}HandlerFunc(srv.{{ pascalize .Name }}Handler) {{- end}} ... } Код в импорте выглядит сложным, хотя на самом деле здесь просто Go-шаблонизация и структуры из репозитория go-swagger. А в функции main мы просто присваиваем хэндлерам наши сгенерированные функции. Осталось сгенерировать код с указанием нашей конфигурации: > goswagger generate server \
-f ./swagger-api/swagger.yml \ -t ./internal/generated -C ./swagger-templates/default-server.yml \ --template-dir ./swagger-templates/templates \ --name example2 Финальный результат можно посмотреть в нашем репозитории. Что мы получили:
Третий шаг. Генерирование клиентов Go-swagger позволяет генерировать и пакет клиента для нашего сервиса, который могут использовать другие Go-сервисы. Здесь я не буду подробно останавливаться на генерировании кода, подход точно такой же, как и при генерировании серверного кода. Для проектов на Go принято складывать публичные пакеты в ./pkg, мы сделаем так же: положим клиент для нашего сервиса в pkg, а сам код сгенерируем следующим образом: > goswagger generate client -f ./swagger-api/swagger.yml -t ./pkg/example3
Пример сгенерированного кода здесь. Теперь все потребители нашего сервиса могут импортировать себе этот клиент, например, по тэгу (для моего примера тэг будет example3/pkg/example3/v0.0.1). Шаблоны клиентов можно настраивать, чтобы, например, прокидывать open tracing id из контекста в заголовок. Выводы Естественно, наша внутренняя реализация отличается от приведенного здесь кода, в основном, за счёт использования внутренних пакетов и подходов к CI (запуск различных тестов и линтеров). В сгенерированном коде из коробки настроен сбор технических метрик, работа с конфигами и логирование. Мы стандартизировали все общие инструменты. За счёт этого мы упростили разработку в целом и выпуск новых сервисов в частности, обеспечили более быстрое прохождение чек-листа сервиса перед деплоем на прод. Давайте проверим, получилось ли достигнуть первоначальных целей:
Внимательный читатель, наверное, уже задался вопросом: как файлы шаблонов попадают в наш проект? Сейчас мы храним их в каждом нашем проекте. Это упрощает повседневную работу, позволяет что-то настраивать под конкретный проект. Но есть и другая сторона медали: отсутствует механизм централизованного обновления шаблонов и доставки новых фич, в основном, связанных с CI. P.S. Если этот материал понравится, то в дальнейшем подготовим статью про стандартную архитектуру наших сервисов, расскажем, какими принципами мы пользуемся при разработке сервисов на Go. =========== Источник: habr.com =========== Похожие новости:
Блог компании Mail.ru Group ), #_blog_kompanii_delivery_club_tech ( Блог компании Delivery Club Tech ), #_go, #_mikroservisy ( Микросервисы ) |
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 23-Ноя 00:01
Часовой пояс: UTC + 5