Skip to content

Latest commit

 

History

History
448 lines (317 loc) · 21.7 KB

File metadata and controls

448 lines (317 loc) · 21.7 KB

Go Clean Template

Go Чистая Архитектура

Шаблон Чистой Архитектуры для приложений на Golang

Release License Go Report Card codecov

Web Framework API Documentation Validation JSON Handling Query Builder Database Migrations Logging Metrics Testing Mocking

Обзор

Цель этого шаблона - показать принципы Чистой Архитектуры Роберта Мартина (дядюшки Боба):

  • как структурировать проект и не дать ему превратиться в спагетти-код
  • где хранить бизнес-логику, чтобы она оставалась независимой, чистой и расширяемой
  • как не потерять контроль при росте проекта

Go-clean-template создан и поддерживается Evrone.

Этот шаблон поддерживает три типа серверов:

  • AMQP RPC (на основе RabbitMQ в качестве транспорта и Request-Reply паттерна)
  • NATS RPC (на основе NATS в качестве транспорта и Request-Reply паттерна))
  • gRPC (gRPC фреймворк на основе protobuf)
  • REST API (Fiber фреймворк)

Содержание

Быстрый старт

Локальная разработка

# Postgres, RabbitMQ, NATS
make compose-up
# Запуск приложения и миграций
make run

Интеграционные тесты (может быть использовано с CI)

# DB, app + migrations, integration tests
make compose-up-integration-test

Весь docker stack с reverse proxy

make compose-up-all 

Проверьте сервисы:

Структура проекта

cmd/app/main.go

Инициализация конфигурации и логгера. Здесь вызывается основная часть приложения из internal/app/app.go.

config

Приложение двенадцати факторов хранит конфигурацию в переменных окружения (часто сокращается до env vars или env). Переменные окружения легко изменить между развёртываниями, не изменяя код; в отличие от файлов конфигурации, менее вероятно случайно сохранить их в репозиторий кода; и в отличие от пользовательских конфигурационных файлов или других механизмов конфигурации, таких как Java System Properties, они являются независимым от языка и операционной системы стандартом.

Конфигурация: config.go

Пример: .env.example

docker-compose.yml использует переменные env для настройки сервисов.

docs

Документация Swagger. Генерируется автоматически с помощью библиотеки swag. Вам не нужно ничего редактировать вручную.

docs/proto

Protobuf файлы. Они используются для генерации Go-кода для gRPC сервисов. Protobuf файлы также используются для генерации документации для gRPC сервисов. Вам не нужно ничего исправлять самостоятельно.

integration-test

Интеграционные тесты. Они запускаются в отдельном контейнере, рядом с контейнером приложения.

internal/app

Здесь находится только одна функция Run. Она размещена в файле app.go и является логическим продолжением функции main.

Здесь создаются все основные объекты. Внедрение зависимостей происходит через конструктор "New ...". Это позволяет слоировать приложение, делая бизнес-логику независимой от других слоев.

Далее запускается сервер и ожидается сигнал в select для корректного завершения работы. Если app.go стал слишком большим, вы можете разделить его на несколько файлов.

Если зависимостей много, то для удобства можно использовать wire.

Файл migrate.go используется для автоматической миграции базы данных. Он включается в компиляцию только при указании тега migrate. Пример:

go run -tags migrate ./cmd/app

internal/controller

Слой хэндлеров сервера (MVC контроллеры). В шаблоне показана работа 3 серверов:

  • AMQP RPC (на основе RabbitMQ в качестве транспорта)
  • gRPC (gRPC фреймворк на основе protobuf)
  • REST API (Fiber фреймворк)

Маршрутизаторы http сервера пишутся в едином стиле:

  • Хэндлеры группируются по области применения (по общему критерию)
  • Для каждой группы создается свой маршрутизатор
  • Объект бизнес-логики передается в маршрутизатор, чтобы быть доступным внутри хэндлеров

internal/controller/amqp_rpc

Простое версионирование RPC. Для версии v2 нужно будет добавить папку amqp_rpc/v2 с таким же содержимым. А в файле internal/controller/amqp_rpc/router.go добавить строку:

routes := make(map[string]server.CallHandler)

{
    v1.NewTranslationRoutes(routes, t, l)
}

{
    v2.NewTranslationRoutes(routes, t, l)
}

internal/controller/grpc

Простое версионирование gRPC.
Для версии v2 нужно будет добавить папку grpc/v2 с таким же содержимым.
Также добавьте папку v2 в proto-файлы в docs/proto.
И в файле internal/controller/grpc/router.go добавьте строку:

{
    v1.NewTranslationRoutes(app, t, l)
}

{
    v2.NewTranslationRoutes(app, t, l)
}

reflection.Register(app)

internal/controller/nats_rpc

Простое версионирование RPC. Для версии v2 нужно будет добавить папку nats_rpc/v2 с таким же содержимым. А в файле internal/controller/nats_rpc/router.go добавить строку:

routes := make(map[string]server.CallHandler)

{
    v1.NewTranslationRoutes(routes, t, l)
}

{
    v2.NewTranslationRoutes(routes, t, l)
}

internal/controller/restapi

Простое версионирование REST API. Для создания версии v2 нужно создать папку restapi/v2 с таким же содержимым. Добавить в файл internal/controller/restapi/router.go строки:

apiV1Group := app.Group("/v1")
{
    v1.NewTranslationRoutes(apiV1Group, t, l)
}
apiV2Group := app.Group("/v2")
{
	v2.NewTranslationRoutes(apiV2Group, t, l)
}

Вместо Fiber можно использовать любой другой http фреймворк.

В файле router.go над хэндлером написаны комментарии для генерации документации через swagger swag.

internal/entity

Сущности бизнес-логики (модели). Могут быть использованы в любом слое. Также они могут иметь методы, например, для валидации.

internal/usecase

Бизнес-логика.

  • Методы группируются по области применения (по общему критерию)
  • У каждой группы своя отдельная структура
  • Один файл - одна структура

Репозитории, webapi, rpc и другие структуры передаются в слой бизнес-логики в связующем файле internal/app/app.go (смотрите Внедрение зависимостей).

internal/repo/persistent

Репозиторий — это абстрактное хранилище (база данных), с которым взаимодействует бизнес-логика.

internal/repo/webapi

Это абстрактное web API, с которым взаимодействует бизнес-логика. Например, это может быть внешний микросервис, к которому бизнес-логика обращается через REST API. Название пакета выбирается таким, чтобы соответствовать его назначению.

pkg/rabbitmq

RabbitMQ RPC паттерн:

  • Внутри RabbitMQ не используется маршрутизация
  • Используется fanout-обмен, к которому привязана одна эксклюзивная очередь - это наиболее производительная конфигурация
  • Переподключение при потере соединения

Внедрение зависимостей

Для устранения зависимости бизнес-логики от внешних пакетов используется внедрение зависимостей.

Например, через конструктор "New" внедряется репозиторий в слой бизнес-логики. Это делает бизнес-логику независимой и переносимой. Мы можем переписать реализацию интерфейса репозитория, не внося изменения в пакет бизнес-логики usecase.

package usecase

import (
// Nothing!
)

type Repository interface {
	Get()
}

type UseCase struct {
	repo Repository
}

func New(r Repository) *UseCase {
	return &UseCase{
		repo: r,
	}
}

func (uc *UseCase) Do() {
	uc.repo.Get()
}

Благодаря разделению через интерфейсы можно генерировать моки (например, используя mockery) и легко писать юнит-тесты.

Мы не привязаны к конкретным реализациям и всегда можем заменить один компонент на другой. Если новый компонент реализует интерфейс, то в бизнес-логике ничего не нужно менять.

Чистая Архитектура

Ключевая идея

Программисты создают оптимальную архитектуру приложения после написания основной части кода.

Хорошая архитектура позволяет откладывать изменения как можно дольше.

Основной принцип

Инверсия зависимостей (та же, что и в SOLID) используется как принцип для внедрения зависимостей. Зависимости направлены от внешнего слоя к внутреннему. Благодаря этому бизнес-логика и сущности остаются независимыми от других частей системы.

Например, приложение можно разделить на два слоя - внутренний и внешний:

  1. Бизнес-логика (например, стандартная библиотека Go).
  2. Инструменты (базы данных, серверы, брокеры сообщений и другие библиотеки и фреймворки).

Чистая архитектура

Внутренний слой с бизнес-логикой должен быть чистым. Он обязан:

  • Не импортировать пакеты из внешних слоев.
  • Использовать только стандартную библиотеку.
  • Взаимодействовать с внешними слоями через интерфейсы (!).

Бизнес-логика не должна ничего знать о Postgres или о реализации web API. Бизнес-логика имеет интерфейс для взаимодействия с абстрактной базой данных или абстрактным web API.

Внешний слой имеет ограничения:

  • Компоненты этого слоя не могут знать друг о друге и взаимодействовать напрямую. Обращение друг к другу происходит через внутренний слой - слой бизнес-логики.
  • Вызовы во внутренний слой выполняются через интерфейсы (!).
  • Данные передаются в формате, удобном для бизнес-логики (структуры хранятся в internal/entity).

Например, нужно обратиться к базе данных из HTTP хэндлера (в слое контроллер). База данных и HTTP находятся во внешнем слое. Они не знают друг о друге ничего и не могут взаимодействовать напрямую. Взаимодействие будет происходить через слой бизнес-логики usecase:

    HTTP > usecase
           usecase > repository (Postgres)
           usecase < repository (Postgres)
    HTTP < usecase

Символы > и < показывают пересечения слоев через интерфейсы и направления. Это же показано на схеме:

Пример

Пример более сложного пути данных:

    HTTP > usecase
           usecase > repository
           usecase < repository
           usecase > webapi
           usecase < webapi
           usecase > RPC
           usecase < RPC
           usecase > repository
           usecase < repository
    HTTP < usecase

Слои

Пример

Терминология в Чистой Архитектуре

  • Entities (сущности) - это структуры, с которыми работает бизнес-логика. Они располагаются в папке internal/entity. В терминологии MVC сущности - это модели.
  • Use Cases - это бизнес-логика. Располагается в папке internal/usecase.

Слой, с которым бизнес-логика взаимодействует напрямую, обычно называется инфраструктурным слоем. Это может быть репозиторий internal/usecase/repo, внешнее webapi internal/usecase/webapi, любой пакет или микросервис. В шаблоне пакеты infrastructure размещены внутри internal/usecase.

Вы можете выбирать, как называть точки входа, по своему усмотрению. Варианты такие:

  • controller (в нашем случае)
  • delivery
  • transport
  • gateways
  • entrypoints
  • primary
  • input

Дополнительные слои

В классической версии Чистой Архитектуры для создания больших монолитных приложений предложено 4 слоя.

В исходной версии внешний слой делится на два, которые также имеют инверсию зависимостей в другие слои и взаимодействуют через интерфейсы.

Внутренний слой также делится на два (с использованием интерфейсов) в случае сложной логики.


Сложные инструменты могут быть разделены на дополнительные слои. Однако добавлять слои следует только в том случае, если это действительно необходимо.

Другие подходы

Кроме Чистой Архитектуры есть и другие подходы:

  • Луковая Архитектура
  • Гексагональная (Порты и адаптеры также похожа на неё) Они обе основаны на принципе инверсии зависимостей. Порты и адаптеры очень похожи на Чистую Архитектуру. Различия в основном заключаются в терминологии.

Похожие проекты

Дополнительная информация