diff --git a/README.md b/README.md index 343c4769..0168b791 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,20 @@ -# Go service template +![Go Clean Template](docs/img/logo.svg) + +# Go Clean template Clean Architecture template for Golang services [![Go Report Card](https://goreportcard.com/badge/github.com/evrone/go-service-template)](https://goreportcard.com/report/github.com/evrone/go-service-template) -![CircleCI](https://img.shields.io/circleci/build/github/evrone/go-service-template) [![License](https://img.shields.io/github/license/evrone/go-service-template.svg)](https://github.com/evrone/go-service-template/blob/master/LICENSE) [![Release](https://img.shields.io/github/v/release/evrone/go-service-template.svg)](https://github.com/evrone/go-service-template/releases/) ## Overview -Цель шаблона показать: -- как организовать проект, чтобы он не превратился в spaghetti code -- где хранить бизнес-логику, чтобы она оставалась независимой, чистой и расширяемой -- как не терять контроль при разрастании микросервиса +The purpose of the template is to show: +- how to organize a project and prevent it from turning into spaghetti code +- where to store business logic so that it remains independent, clean, and extensible +- how not to lose control when a microservice grows -Используя принципы Роберта Мартина (aka Uncle Bob). +Using the principles of Robert Martin (aka Uncle Bob). ## Content - [Quick start](#quick-start) @@ -22,7 +23,7 @@ Clean Architecture template for Golang services - [Clean Architecture](#clean-architecture) ## Quick start -Локальная разработка: +Local development: ```sh # Postgres, RabbitMQ $ make compose-up @@ -30,7 +31,7 @@ $ make compose-up $ make run ``` -Интеграционные тесты (можно запускать в ci): +Integration tests (can be run in CI): ```sh # DB, app + migrations, integration tests $ make compose-up-integration-test @@ -38,110 +39,108 @@ $ make compose-up-integration-test ## Project structure ### `cmd/app/main.go` -Инициализация конфигурации и логгера. Далее функция _main_ "продолжается" в +Configuration and logger initialization. Then the main function "continues" in `internal/app/app.go`. ### `config` -Конфигурация. Сперва читается `config.yml`, далее переменные окружения (перезаписывают -yaml конфиг при совпадении). В `config.go` структуры конфига. Тег `env-required: true` -обязывает указать значение (или в yaml, или в переменных окружения). +Configuration. First, `config.yml` is read, then environment variables overwrite the yaml config if they match. +The config structure is in the `config.go`. +The `env-required: true` tag obliges you to specify a value (either in yaml, or in environment variables). -Для конфигурирования была выбрана библиотека [cleanenv](https://github.com/ilyakaznacheev/cleanenv), -которая имеет не так много звёзд на GitHub, однако проста и отвечает всем требованиям. +For configuration, we chose the [cleanenv](https://github.com/ilyakaznacheev/cleanenv) library. +It does not have many stars on GitHub, but is simple and meets all the requirements. -Чтение конфига из yaml противоречит идеологии 12-ти факторов, однако на практике это более -удобно, чем чтение всего конфига из ENV. Подразумевается что в yaml находятся дефолтные -значения, а в ENV определяются чувствительные к безопасности переменные. +Reading the config from yaml contradicts the ideology of 12 factors, but in practice, it is more convenient than +reading the entire config from ENV. +It is assumed that default values are in yaml, and security-sensitive variables are defined in ENV. ### `docs` -Swagger документация. Авто сгенерированная библиотекой [swag](https://github.com/swaggo/swag). -Править руками не нужно. +Swagger documentation. Auto-generated by [swag](https://github.com/swaggo/swag) library. +You don't need to correct anything by yourself. ### `integration-test` -Интеграционные тесты. Запускаются в виде отдельного контейнера, рядом с контейнером -приложения. Rest API удобно тестировать с помощью [go-hit](https://github.com/Eun/go-hit) -(жаль не так популярна, библиотека топчик). +Integration tests. +They are launched as a separate container, next to the application container. +It is convenient to test the Rest API using [go-hit](https://github.com/Eun/go-hit). ### `internal/app` -В файле `app.go` всегда одна функция _Run_, которая "продолжает" функцию _main_. +There is always one _Run_ function in the `app.go` file, which "continues" the _main_ function. -Здесь происходит создание всех основных объектов. Через конструкторы "New..." происходит -внедрение зависимостей (см. [Dependency Injection](#dependency-injection)). -Этот приём позволяет нам разделить приложение на слои, придерживаясь принципа инверсии -зависимостей (Dependency Inversion). Благодаря этому бизнес-логика становится независимой -от других слоев. +This is where all the main objects are created. +Dependency injection occurs through the "New ..." constructors (see Dependency Injection). +This technique allows us to layer the application using the [Dependency Injection](#dependency-injection) principle. +This makes the business logic independent from other layers. -Далее мы запускаем сервер и ожидаем в _select_ сигналы для graceful завершения. +Next, we start the server and wait for signals in _select_ for graceful completion. +If `app.go` starts to grow, you can split it into multiple files. -Если `app.go` начнет разрастаться, можно разделить его не несколько файлов. +For a large number of injections, [wire](https://github.com/google/wire) can be used. -При большом количестве инъекций, можно использовать [wire](https://github.com/google/wire). +The `migrate.go` file is used for database auto migrations. +It is included if an argument with the _migrate_ tag is specified. +For example: -Файл `migrate.go` используется для авто миграций БД. Он подключается если указан аргумент с -тегом _migrate_. Например: ```sh $ go run -tags migrate ./cmd/app ``` ### `internal/delivery` -Слой с обработчиками серверов (контроллеры по MVC). В шаблоне показаны 2 сервера: -- RPC (RabbitMQ в качестве транспорта) -- REST http (Gin фреймворк) +Server handler layer (MVC controllers). The template shows 2 servers: +- RPC (RabbitMQ as transport) +- REST http (Gin framework) -Роутеры серверов написаны в одном стиле: -- Обработчики группируются по области применения (по общему признаку) -- Для каждой группы создаётся своя структура-роутер, методы которой обрабатывают пути -- В структуру-роутер инжектится структура бизнес-логики, которую будут вызывать обработчики +Server routers are written in the same style: +- Handlers are grouped by area of application (by a common basis) +- For each group, its own router structure is created, the methods of which process paths +- The structure of the business logic is injected into the router structure, which will be called by the handlers #### `internal/delivery/http` -Простое версионирование REST. Для версии v2 потребуется добавить папку `http/v2` с -тем же содержанием. И в файле `internal/app` добавить строку: +Simple REST versioning. +For v2, we will need to add the `http/v2` folder with the same content. +And in the file `internal/app` add the line: ``` handler := gin.New() v1.NewRouter(handler, translationService) v2.NewRouter(handler, translationService) ``` -Вместо Gin можно использовать любой другой http фреймворк или даже стандартную `net/http` -библиотеку. -В `v1/ruoter.go` и над методами обработчиков присутствуют комментарии для генерации -swagger документации с помощью [swag](https://github.com/swaggo/swag). +Instead of Gin, you can use any other http framework or even the standard `net/http` library. + +In `v1/router.go` and above the handler methods, there are comments for generating swagger documentation using [swag](https://github.com/swaggo/swag). ### `internal/domain` -Сущности бизнес-логики (модели), их можно использовать в любых слоях. -Так же здесь могут быть методы, например для валидации. +Entities of business logic (models) can be used in any layer. +There can also be methods, for example, for validation. ### `internal/service` -Бизнес-логика. -- Методы группируются по области применения (по общему признаку) -- Для каждой группы — своя структура -- В одном файле — одна структура +Business logic. +- Methods are grouped by area of application (on a common basis) +- Each group has its own structure +- One file - one structure -В структуры бизнес-логики инжектятся (см. [Dependency Injection](#dependency-injection)) -репозитории, webapi, rpc, другие структуры бизнес-логики. +Repositories, webapi, rpc, and other business logic structures are injected into business logic structures +(see [Dependency Injection](#dependency-injection)). #### `internal/service/repo` -Репозиторий — это абстрактное хранилище (база данных), с которой работает бизнес-логика. +A repository is an abstract storage (database) that business logic works with. #### `internal/service/webapi` -Это абстрактный web api, с которым работает бизнес-логика. Например, это может быть другой -микросервис, к которому по REST API обращается бизнес-логика. Название пакета меняется в -зависимости от назначения. +It is an abstract web API that business logic works with. +For example, it could be another microservice that business logic accesses via the REST API. +The package name changes depending on the purpose. ### `pkg/rabbitmq` -RPC паттерн RabbitMQ: -- Внутри RabbitMQ нет никакой маршрутизации -- Используются Exchange fanout, к которому биндится 1 exclusive queue, это самый -производительный конфиг -- Реконнект при потере соединения +RabbitMQ RPC pattern: +- There is no routing inside RabbitMQ +- Exchange fanout is used, to which 1 exclusive queue is bound, this is the most productive config +- Reconnect on the loss of connection ## Dependency Injection -Для того чтобы убрать зависимость бизнес-логики от внешних пакетов, используется -инъекция зависимостей. +In order to remove the dependence of business logic on external packages, dependency injection is used. -Например, через конструктор NewService мы делаем инъекцию зависимости в структуру -бизнес-логики. Таким образом бизнес-логика становится независимой (и переносимой). -Мы можем подменить реализацию интерфейса и при этом не вносить правки в пакет `service`. +For example, through the NewService constructor, we inject the dependency into the structure of the business logic. +This makes the business logic independent (and portable). +We can override the implementation of the interface without making changes to the `service` package. ```go package service @@ -166,62 +165,58 @@ func (s *Service) Do() { s.repo.Get() } ``` -Это так же позволит нам делать автогенерацию моков (например с помощью [mockery](https://github.com/vektra/mockery)) -и легко писать юнит-тесты. -> Мы не привязываемся к конкретным реализациям, чтобы всегда иметь возможность -> безболезненно менять один компонент на другой. Если новый компонент будет -> реализовывать интерфейс, в бизнес-логике менять ничего не потребуется. +It will also allow us to do auto-generation of mocks (for example with [mockery](https://github.com/vektra/mockery)) and easily write unit tests. + +> We are not tied to specific implementations in order to always be able to change one component to another. +> If the new component implements the interface, nothing needs to be changed in the business logic. ## Clean Architecture -### Ключевая идея -Программисты осознают оптимальную архитектуру приложения уже после того как большая часть -кода была написана. +### Key idea +Programmers realize the optimal architecture for an application after most of the code has been written. -> Хорошая архитектура позволяет оттягивать принятие решений на как можно позднее время. +> A good architecture allows decisions to be delayed to as late as possible. -### Главный принцип -Dependency Inversion (тот самый из SOLID) — принцип инверсии зависимостей. Направление -зависимостей идет из внешнего слоя во внутренний. Благодаря чему бизнес-логика и сущности -остаются независимыми от других частей системы. +### The main principle +Dependency Inversion (the same one from SOLID) is the principle of dependency inversion. +The direction of dependencies goes from the outer layer to the inner layer. +Due to this, business logic and entities remain independent from other parts of the system. -Итак, приложение делится на 2 слоя, внутренний и внешний: -1. **Бизнес-логика** (стандартная библиотека Go). -2. **Инструменты** (базы данных, серверы, брокеры сообщений, любые другие пакеты -и фреймворки). +So, the application is divided into 2 layers, internal and external: +1. **Business logic** (Go standard library). +2. **Tools** (databases, servers, message brokers, any other packages and frameworks). -![Clean Architecture](docs/img/layers.png) +![Clean Architecture](docs/img/layers-1.png) -**Внутренний** слой с бизнес-логикой должен быть чистым, то есть: -- Не иметь в себе импортов пакетов из внешнего слоя. -- Использовать возможности лишь стандартной библиотеки. -- Делать обращения к внешнему слою через интерфейс(!). +**The inner layer** with business logic should be clean. It should: +- Not have package imports from the outer layer. +- Use only the capabilities of the standard library. +- Make calls to the outer layer through the interface (!). -Бизнес-логика ничего не знает о Postgres или конкретном web api. Бизнес-логика имеет -интерфейс для работы с _абстрактной_ базой данных или _абстрактным_ web api. +The business logic doesn't know anything about Postgres or a specific web API. +Business logic has an interface for working with an _abstract_ database or _abstract_ web API. -**Внешний** слой имеет другие ограничения: -- Все компоненты этого слоя не знают о существовании друг друга. Как из одного инструмента -вызвать другой? Напрямую никак, только через внутренний слой бизнес-логики. -- Все обращения к внутреннему слою происходят через интерфейс(!). -- Данные передаются в том формате, который удобен для бизнес-логики (`internal/domain`). +**The outer layer** has other limitations: +- All components of this layer are unaware of each other's existence. How to call another from one tool? Not directly, only through the inner layer of business logic. +- All calls to the inner layer are made through the interface (!). +- Data is transferred in a format that is convenient for business logic (`internal/domain`). + +For example, you need to access the database from HTTP (controller). +Both HTTP and database are in the outer layer, which means they know nothing about each other. +The communication between them is carried out through `service` (business logic): -**Например**, нужно обратиться из HTTP (контроллера) к базе данных. И HTTP, и -БД находятся во внешнем слое, значит они ничего не знают друг о друге. Связь -между ними осуществляется через `service` (бизнес-логику): ``` HTTP > service service > repository (Postgres) service < repository (Postgres) HTTP < service ``` -Стрелочками > и < показано пересечение границ слоев через Интерфейсы. - -То же самое на картинке: +The symbols > and < show the intersection of layer boundaries through Interfaces. +The same is shown in the picture: ![Example](docs/img/example-http-db.png) -Или более сложная бизнес-логика: +Or more complex business logic: ``` HTTP > service service > repository @@ -235,22 +230,25 @@ Dependency Inversion (тот самый из SOLID) — принцип инве HTTP < service ``` -### Терминология Чистой Архитектуры -- **Entities** — структуры, которыми оперирует бизнес-логика. Находятся в папке -`internal/domain`. Domain намекает на то что мы придерживаемся принципов DDD (domain -driven design), это отчасти так, но без фанатизма. В терминах MVC, entities - это модели. - -- **Use Cases** — это бизнес-логика, находится в `internal/service`. Называть бизнес-логику -словом _service_ не очень идеоматично с точки зрения Чистой Архитектуры, но одно слово -_service_ удобнее использовать для названия пакета, чем два: _use case_. - -Слой с которым непосредственно взаимодействует бизнес-логика обычно называют -инфраструктурным слоем - _infrastructure_. Это могут быть репозитории -`internal/service/repo`, внешние webapi `internal/service/webapi`, любые pkg, другие -микросервисы. В шаблоне инфраструктурные пакеты находятся внутри `internal/service`. - -Как называть точки входа, вопрос открыт. Можно выбрать на свой вкус. Варианты: -- delivery (в нашем случае) +### Layers +![Example](docs/img/layers-2.png) + +### Clean Architecture Terminology +- **Entities** are structures that business logic operates on. + They are located in the `internal/domain` folder. + Domain hints that we adhere to the principles of DDD (domain-driven design), and this is partly true. + In MVC terms, entities are models. + +- **Use Cases** is business logic located in `internal/service`. + Calling business logic with the word _service_ is not very idiomatic from the point of view of Clean Architecture, + but using one word _service_ is more convenient for a package name than two: _use case_. + +The layer with which business logic directly interacts is usually called the _infrastructure_ layer. +These can be repositories `internal/service/repo`, external webapi `internal/service/webapi`, any pkg, and other microservices. +In the template, the _infrastructure packages are located inside `internal/service`. + +You can choose how to call the entry points as you wish. The options are: +- delivery (in our case) - controllers - transport - adaptors @@ -259,33 +257,29 @@ _service_ удобнее использовать для названия пак - primary - input -### Дополнительные слои -Классический вариант [Clean Architecture](https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html) -разрабатывался для построения больших монолитных приложений и имеет 4 слоя. +### Additional layers +The classic version of [Clean Architecture](https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html) was designed for building large monolithic applications and has 4 layers. -То есть, в оригинальной версии, внешний слой делится ещё на два, которые -так же имеют обратную инверсию зависимостей друг к другу (направленную во внутрь) -и общаются через интерфейсы. +In the original version, the outer layer is divided into two more, which also have an inversion of dependencies +to each other (directed inward) and communicate through interfaces. + +The inner layer is also divided into two (with separation of interfaces), in the case of complex logic. -Внутренний слой так же делится на два (с разделением интерфейсов), в случае -сложной логики. _______________________________ -Сложные инструменты можно делить на дополнительные слои. Однако, следует -руководствоваться здравым смыслом и добавлять слои лишь в том случае, если это -действительно необходимо. +Complex tools can be divided into additional layers. +However, you should add layers only if really necessary. + +### Alternative approaches +In addition to Clean architecture, _Onion architecture_ and _Hexagonal_ (_Ports and adapters_) are similar to it. +Both are based on the principle of Dependency Inversion. +_Ports and adapters_ are very close to _Clean Architecture_, the differences are mainly in terminology. -### Альтернативные подходы -Кроме Чистой архитектуры есть очень близкие по духу — Луковая архитектура и -Гексагональная (Порты и адаптеры). В основе обеих лежит базовый принцип -инверсии зависимостей. _Порты и адаптеры_ очень близка к _Чистой Архитектуре_, различия -в основном в терминологии. -## Похожие проекты +## Similar projects - [https://github.com/bxcodec/go-clean-arch](https://github.com/bxcodec/go-clean-arch) - [https://github.com/zhashkevych/courses-backend](https://github.com/zhashkevych/courses-backend) -## Полезные ссылки -- [Статья The Clean Architecture](https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html) -- [Книга Чистая архитектура](https://www.ozon.ru/context/detail/id/144499396/) -- [12 факторов](https://12factor.net/ru/) \ No newline at end of file +## Useful links +- [The Clean Architecture article](https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html) +- [Twelve factors](https://12factor.net/ru/) \ No newline at end of file diff --git a/docs/img/layers-1.png b/docs/img/layers-1.png new file mode 100644 index 00000000..3ee80f51 Binary files /dev/null and b/docs/img/layers-1.png differ diff --git a/docs/img/layers-2.png b/docs/img/layers-2.png new file mode 100644 index 00000000..4f81b667 Binary files /dev/null and b/docs/img/layers-2.png differ diff --git a/docs/img/layers.png b/docs/img/layers.png deleted file mode 100644 index 69117ec8..00000000 Binary files a/docs/img/layers.png and /dev/null differ diff --git a/docs/img/logo.svg b/docs/img/logo.svg new file mode 100644 index 00000000..d98ffda0 --- /dev/null +++ b/docs/img/logo.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + +