Для того чтобы повысить качество приложения, написанного на языке go, можно использовать разные линтеры. Один из таких линтеров — архитектурный.
В приложении архитектура — это то, как код разложен по «слоям», и какие слои могут вызывать друг друга.
В данной статье расскажу про свой бесплатный, open-source, линтер с MIT лицензией и чем он может быть полезен.

TLDR — Getting started
Линтер работает с любым кодом, не только с web-приложениями, но для примера посмотрим на web-приложение с одним АПИ методом:
GET /books?author=Joe
У приложения могут быть такие слои:
-
handler (тут обработчики API занимаются валидацией и трансформацией in/out данных)
-
service (бизнес-логика сервиса)
-
repository (инфровый слой для доступа к данным)
-
models (тут будут лежать domain DTO’шки)
Мы хотим, чтобы запросы в repository мог отправлять только service слой, но не мог handler, получается такая картина:

Такую схему зависимостей можно описать в виде yml файлика:
version: 3 workdir: internal components: handler: { in: handlers/* } service: { in: services/* } repository: { in: repositories/* } models: { in: models/** } commonComponents: - models deps: handler: mayDependOn: - service service: mayDependOn: - repository
p.s. к обозначению полей в этом файле вернемся чуть позже.
Для наглядности и простоты приложение будет выполнять 1 запрос прямо в main.go и завершаться. Тут же будет сборка зависимостей.
func main() { repository := booksRepository.NewRepository() service := booksService.NewService(repository) handler := booksHandler.NewHandler(service) books, err := handler.Books("Joe") if err != nil { panic(err) } for _, book := range books { fmt.Printf("Book %d has author %s\n", book.ID, book.Author) } os.Exit(0) }
Сейчас приложение полностью соответствует описанной архитектуре, если запустить линтер и посмотреть результаты, получим ожидаемое «OK»:
$ go-arch-lint check OK - No warnings found
Теперь можно поменять код так, чтобы он нарушал правила:
func main() { // .. repository := booksRepository.NewRepository() handler := booksHandler.NewHandler( service, repository, // нарушаем правило тут! ) // .. }
И запустим ещё раз:

Тут мы видим проблемный код, где конкретно и что было нарушено, и можем это поправить.
Как добавить линтер в уже существующий проект
С уже существующей кодовой базой есть одна проблема — в ней точно будет «грязный» код, который самым неявным способом нарушит «чистую» архитектуру.
Поэтому добавление линтера в проект должно происходить в несколько шагов:

-
Текущее состояние проекта
-
Добавление .go-arch-lint.yml файл с описанием идеальной архитектуры
-
Линтер находит проблемные места в проекте. Их не стоит исправлять сразу, а можно «легализовать» добавлением в конфиг и todo комментарием (можно с ссылкой на задачу в jira или каком-то трекере)
-
Асинхронно, в свободное время, тех. долг и т.п. можно спокойно исправить код
-
После исправлений осталось только подчистить граф зависимостей
Такой алгоритм позволит добавить линтер в существующий проект сразу, хоть мы пока ничего не улучшили, но зато теперь видим проблемы, и можем их исправить в будущем. При этом линтер не даст добавить новых проблем и уже этим приносит пользу.
Проверка вендорных зависимостей
Для большего качества можно проверять не только локальные зависимости в проекте, но и то, какие вендорные библиотеки используются в вашем коде.

Для начала нужно расширить конфиг:
-
переключить
allow.depOnAnyVendorвfalse— теперь линтер будет проверять вендорные зависимости -
добавить поле
vendorsс описанием вендорных зависимостей -
в поле
commonVendorsможно указать вендорные либы, которых можно будет импортировать из любого места в проекте -
в графе зависимостей поле
canUseотвечает за список вендорных зависимостей, которые этот компонент может использовать (импортировать)
version: 3 allow: depOnAnyVendor: false .. vendors: company: { in: my-company.example.com/*/pkg/** } validation: { in: github.com/go-ozzo/ozzo-validation } observability: in: - github.com/prometheus/** - github.com/uber-go/zap - go.opentelemetry.io/otel - go.opentelemetry.io/otel/* commonVendors: - company - observability deps: handler: mayDependOn: - service canUse: - validation
Как работает линтер
Архитектурный линтер состоит из 3 частей:
Component — это абстракция над package (пакетом). Один компонент включает в себя один или более пакетов.
Dependency — зависимость. Они бывают двух видов: явные и неявные.
-
Явные зависимости — это
importв файле с описанием конкретной зависимости от другого пакета. -
Неявные — это передача методов или структур с методами через интерфейсы, каналы и прочие способы
Dependency tree — граф отношений между компонентами (кому и от кого можно зависеть)
Для примера более сложная архитектура с такими слоями:
components: handler: { in: handlers/* } service: { in: services/** } repository: { in: services/*/repository } models: in: - services/*/domain - services/*/dto
Если запустить маппинг, можем увидеть связь между компонентами и go пакетами:
$ go-arch-lint mapping
Package | Compoenent ---------------------------------------------- internal | - handlers | - articles | handler auth | handler books | handler info | handler user | handler services | - auth | - logic | service dto | models repository | repository books | - somecode | service domain | models repository | repository user | - admin | service groups | service dto | models repository | repository
При разметке несколько wildcard масок могут покрыть один и тот же package, в данном примере internal/services/auth/repository/example покрывают 2 компонента:
-
service в
services/** -
repository в
services/*/repository
Но каждый пакет может быть привязан только к одному компоненту. Линтер выберет компонент с самым точным описанием пути, который находит по маске меньше всего пакетов, или имеет более вложенную структуру.
В итоге линтер работает таким образом

-
размечает весь код на компоненты
-
находит все зависимости между компонентами
-
строит граф зависимостей
-
сравнивает актуальный и желаемый граф зависимостей
-
если мы получили непустой DIFF — значит, есть проблемы
Установка и запуск
Установка:
go install github.com/fe3dback/go-arch-lint@latest
Запуск:
cd code/acme/my-project go-arch-lint check
Другие способы запуска и установки можно найти на странице проекта: https://github.com/fe3dback/go-arch-lint
Плагин для jetbrains IDE / goland
Для более удобной работы с конфигом можно установить плагин: https://plugins.jetbrains.com/plugin/15423-goarchlint-file-support

Интеграция с другими тулзами
Все команды линтера можно запускать с флагом `—json`. Это позволит просто получить любые данные по проблемам, проекту, схеме и т.п. что удобно для интеграции с другими тулзами, CI/CD утилитами и т.п.
go-arch-lint check --json
{ "Type": "models.Check", "Payload": { // .. "ArchWarningsDeepScan": [ { "Gate": { "ComponentName": "handler", "MethodName": "NewHandler", "Definition": { .. } }, "Dependency": { "ComponentName": "repository", "Name": "books.Repository", "InjectionAST": "repository", "Injection": { "Valid": true, "File": "/go/src/acme/my-project/main.go", "Line": 15, "Offset": 37 } }, "Target": { .. } } ], // .. } }
Экспорт графа в виде картинки
Можно экспортировать граф зависимостей в виде svg файлика или d2 описания.
go-arch-lint graph go-arch-lint graph --d2
handler -> service service -> repository

Плагины к другим IDE/редакторам
Если у кого-нибудь есть желание написать плагин для других редакторов (к примеру vscode), в линтере есть доп. методы для удобства интеграций.
# Основная информация о проекте go-arch-lint self-inspect --json # Json-schema для валидации yaml конфига go-arch-lint schema --version 3 # {"$schema":"http://json-schema.org/draft-07/schema#", ... # Проверка кода проекта, результаты в json go-arch-lint check --json # Маппинг go пакетов на компоненты go-arch-lint mapping --json
Полный список команд и флагов можно посмотреть в —help
go-arch-lint --help Usage: go-arch-lint [command] Available Commands: check check project architecture by yaml file completion Generate the autocompletion script for the specified shell graph output dependencies graph as svg file help Help about any command mapping mapping table between files and components schema json schema for arch file inspection self-inspect will validate arch config and arch setup version Print go arch linter version Flags: -h, --help help for go-arch-lint --json (alias for --output-type=json)
Contributing
Если есть желание помочь с разработкой, можно посмотреть на код в репозитории.
ссылка на оригинал статьи https://habr.com/ru/articles/751174/
Добавить комментарий