Боже, храни документацию

от автора

Привет, Хабр!

Понимаете ли вы пользу документации? Конечно да. Наверняка каждый встречался с ужасно или вовсе не задокументированным проектом. Это всегда больно и с языка так и срываются матерные слова. А бывает, что документация вроде и есть, но разбросана по корпоративному вики, по разным репозиториям, разным директориям. В общем мрак.

В этой статье хотелось бы поговорить про конкретную область для документирования — микросервисы и REST API. Не буду ходить вокруг да около, конечно же всем известна спецификация OpenAPI, пережившая уже целых три поколения и Swagger, де-факто ставший стандартом индустрии. Речь пойдёт о том, как сделать систему с автоматически актуализируемой Swagger-документацией через использование CI/CD пайплайна и парочки полезных утилит.

Предисловие

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

О чём, собственно, речь

А речь о проекте swaggo/swag, который позволяет описывать документацию в формате Swagger через javadoc-like комментарии к хэндлерам. Самая простая интеграция у проекта с фреймворком gin, но у меня не возникало особых проблем и при использовании echo, и даже «голого» net/http.

Что нужно знать про swag

Итак, немного углубимся в тему. Представим, что у нас есть какое-то несложное (или сложное) веб-приложение. Пусть написали мы его на gin. Значит, в приложении однозначно будет хотя бы один handler, например такой:

func HelloWorld(g *gin.Context)  {    g.JSON(http.StatusOK, "helloworld") }

И вот вы захотели для этого жутко сложного метода получить Swagger-документацию. Самый простой путь, что называется «в лоб» — пойти и написать OpenAPI v2 спецификацию вручную, получится что-то вроде такого:

swagger: "2.0" info:   title: My API   description: My API   license:     name: MIT   version: 0.0.1 paths:   /:     get:       summary: Ping       description: Is used to test connection.       responses:         '200':           description: Success       

В общем-то незамысловато и так вполне можно поступить, если бы только все приложения были такими маленькими и никогда не расширялись. Каждое изменение в хэндлерах придётся документировать дважды — в комментариях к коду и в этом yaml (или json, если БДСМ сердцу ближе). Но можно этого избежать, примерно вот так:

// @Summary Ping // @Description Is used to test connection. // @Accept json // @Produce json // @Success 200 {string} helloworld // @Router / [get] // // I'm vanila godoc func HelloWorld(g *gin.Context)  { g.JSON(http.StatusOK, "helloworld") }

Стало чуть приятнее, не правда ли? Теперь и код сам по себе задокументирован, и никаких отдельных файлов городить не нужно. С помощью таких же комментариев можно описать все остальные хэндлеры, если они есть, а «шапка», то есть блок info, описывается прямо над функцией main — точкой входа в приложение. За подробностями отсылаю к документации, где прекрасно описаны все имеющиеся на данный момент аннотации и дополнительные фишки, такие как описание структуры тела запроса в виде структуры в Go.

После того, как комментарии оставлены, их нужно как-то собрать воедино и превратить это в то, что выше было описано руками. Для этого потребуется cli-утилита swag, самый простой способ установить которую — использовать go install (не забудьте добавить директорию с пакетом в PATH, вероятнее всего это будет ~/go/bin):

go install github.com/swaggo/swag/cmd/swag@latest

Генерация выполняется следующим образом:

swag init -d "directories/with,comments" --parseDependency

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

В результате будут сгенерированы три файла, по умолчанию в директории docs/: docs.go, swagger.yaml и swagger.json. Как не трудно догадаться — это и есть наша документация в разных форматах.

Swagger UI

Наличие docs.go намекает на то, что рядом с приложением можно поднять и Swagger UI. Делается это крайне просто (пример с небольшими изменениями взят из документации):

import (   "net/http"      docs "module_name/docs"      swaggerfiles "github.com/swaggo/files"   ginSwagger "github.com/swaggo/gin-swagger"   "github.com/gin-gonic/gin" )  func main()  {    r := gin.Default()    docs.SwaggerInfo.BasePath = "/api/v1"    v1 := r.Group("/api/v1")    {       eg := v1.Group("/example")       {          eg.GET("/helloworld", HelloWorld)       }    }    r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerfiles.Handler))    r.Run(":8080")  }

Теперь при запуске приложения нетрудно найти Swagger UI, который находится по адресу /swagger/.

Путь к автоматизации

Теперь вернёмся к начальному тезису этой статьи. Одной документации мало, нужно как-то обеспечить её постоянное обновление. Отсутствующие комментарии в коде легко выявляются на этапе ревью кода, а вот просить разработчиков ставить себе swag cli и перегенеривать docs/ каждый раз кажется затратным, да и не барское это дело — автогенерируемый код ревьюить, поэтому пойдём иным путём. Напишем-ка Dockerfile, который бы и документацию генерировал, и приложение билдил, да ещё и с Multi-stage, чтобы по всем канонам. Получим что-то примерно такое:

FROM golang:1.21-alpine AS swag  WORKDIR /app  RUN apk add --no-cache git RUN go install github.com/swaggo/swag/cmd/swag@latest  COPY . .  RUN go mod tidy && swag init  FROM golang:1.20-alpine AS builder  WORKDIR /app  RUN apk add --no-cache git  COPY go.mod go.sum ./ RUN go mod tidy  COPY --from=swag . .  RUN go build -o /app/main .  FROM scratch  COPY --from=builder /app/main .  RUN apk add --no-cache ca-certificates  CMD ["./main"] 

Безусловно это очень плохой Dockerfile, но цель была сделать его как можно понятнее и проще, а не применить все Best Practices.

С Dockerfile покончено. Теперь нужно сбилдить image и запушить в какое-нибудь хранилище. Для следующих шагов очень важно использовать инкрементируемый тег. Например используйте семантическое версионирование.

Деплой приложения

Микросервисы отлично смотрятся в кластере Kubernetes, поэтому задеплоим нашу поделку. Так как для работы всей схемы потребуется ArgoCD и ArgoCD Image Updater, сразу будем писать Helm-чарт. Упрощённо это будет выглядеть вот так:

// values.yaml  replicaCount: 1  image:   repository: registry.io/app   tag: 0.0.1   pullPolicy: Always  service:   type: ClusterIP   port: 8080
// templates/deployment.yaml  apiVersion: apps/v1 kind: Deployment metadata:   name: {{ .Release.Name }}   labels:     app: {{ .Chart.Name }}   annotations:     {{- with .Values.annotations }}     {{ toYaml . | indent 4 }}     {{- end }} spec:   replicas: {{ .Values.replicaCount }}   selector:     matchLabels:       app: {{ .Chart.Name }}   template:     metadata:       labels:         app: {{ .Chart.Name }}     spec:       containers:         - name: {{ .Chart.Name }}           image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"           imagePullPolicy: {{ .Values.image.pullPolicy }}           ports:             - containerPort: 8080
// templates/service.yaml  apiVersion: v1 kind: Service metadata:   name: {{ .Release.Name }}   labels:     app: {{ .Chart.Name }} spec:   type: {{ .Values.service.type }}   ports:     - port: {{ .Values.service.port }}       targetPort: 8080   selector:     app: {{ .Chart.Name }} 

Помимо этого описываем ArgoCD Application:

apiVersion: argoproj.io/v1alpha1 kind: Application metadata:   name: app   namespace: argocd   annotations:     argocd-image-updater.argoproj.io/image-list: app="registry.io/app"     argocd-image-updater.argoproj.io/app.update-strategy: "semver" spec:   project: default   source:     repoURL: <git-репозиторий с чартом>     targetRevision: master     path: charts/app     helm:       valueFiles:         - values.yaml   destination:     server: https://kubernetes.default.svc     namespace: swag-example   syncPolicy:     automated:       prune: true       selfHeal: true     syncOptions:       - CreateNamespace=true

Post-commit trigger

Этот раздел будет сугубо теоретическим, так как мы используем разные платформы для написания CI и глупо привязываться к чему-то конкретному.
Тем не менее, логика довольно тривиальна — мы уже написали Dockerfile, который умеет билдить приложение и генерировать документацию, уже применили GitOps и даже объяснили ArgoCD, что ему нужно следить за semver-тегами. Теперь дело за малым — научиться обновлять версии образов в автоматическом режиме. Для этого я предлагаю воспользоваться вебхуками, которые умеет отправлять и GitHub, и GitLab, и BitBucket, и даже отечественный GitFlic.

После того, как код прошёл ревью, в комментарии были внесены необходимые правки, а разработчик запушил свои изменения в master-ветку, платформа для CI получает вебхук и запускается пайплайн, который должен сбилдить образ и запушить новую версию в registry. Реализаций этой логики может быть много. Более того, эту логику можно расширить и разделить dev- и prod-контуры. Как бы там ни было, построение системы с постоянно обновляемой документацией завершено.

Заключение

Применение такого подхода позволит качественно документировать свой бэкенд и делать это автоматически, что уменьшает человеческий фактор и не позволит допустить устаревания документации. Да, безусловно предложенная концепция требует сформированной инфраструктуры в виде какой-либо платформы для CI и ArgoCD/FluxCD, но кажется, что такой стек встречается повсеместно. В крайнем случае, никто не мешает использовать тот же GitLab CI как инструмент деплоя — на мой личный взгляд это не так красиво, но зато просто.

Надеюсь статья покажется кому-то полезной и побудит попробовать сделать что-то подобное!

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

Было полезно?

44.44% Да4
33.33% Нет3
22.22% Я просто мимо проходил2

Проголосовали 9 пользователей. Воздержавшихся нет.

ссылка на оригинал статьи https://habr.com/ru/articles/863974/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *