diff --git a/.env b/.env deleted file mode 100644 index 76cde1b0..00000000 --- a/.env +++ /dev/null @@ -1,5 +0,0 @@ -DISABLE_SWAGGER_HTTP_HANDLER=true -GIN_MODE=release -LOG_ROLLBAR_TOKEN= -PG_URL=postgres://user:pass@localhost:5432/postgres -RMQ_URL=amqp://guest:guest@localhost:5672/ diff --git a/.env.example b/.env.example index 76cde1b0..49aa9d45 100644 --- a/.env.example +++ b/.env.example @@ -1,5 +1,4 @@ DISABLE_SWAGGER_HTTP_HANDLER=true GIN_MODE=release -LOG_ROLLBAR_TOKEN= PG_URL=postgres://user:pass@localhost:5432/postgres RMQ_URL=amqp://guest:guest@localhost:5672/ diff --git a/Dockerfile.prod b/Dockerfile.prod deleted file mode 100644 index 52b844f6..00000000 --- a/Dockerfile.prod +++ /dev/null @@ -1,11 +0,0 @@ -# Step 1: Builder -FROM golang:1.16.2-alpine3.13 as builder -COPY . /app -WORKDIR /app -RUN go mod download -x && \ - CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -x -o /bin/app ./cmd/app - -# Step 2: Final -FROM alpine:3.13 -COPY --from=builder /bin/app /app -CMD ["/app"] \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..4104120c --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 Evrone + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/Makefile b/Makefile index f16c49da..872c1766 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -include .env +include .env.example export compose-up: diff --git a/README.md b/README.md index 732ad91a..f1299d52 100644 --- a/README.md +++ b/README.md @@ -28,21 +28,19 @@ $ make run $ make compose-up-integration-test ``` - - Clean Architecture ---- ### Главный принцип -Dependency Inversion (тот самый из SOLID) — принцип инверсии зависимостей, играет -ключевую роль в построении архитектуры приложения. Чтобы принцип начал работать, +Dependency Inversion (тот самый из SOLID) — принцип инверсии зависимостей, играет +ключевую роль в построении архитектуры приложения. Чтобы принцип начал работать, нам нужно поделить приложение на слои. -![Clean Architecture](pkg/img/layers.png) +![Clean Architecture](docs/img/layers.png) Итак, приложение делится на 2 слоя, внутренний и внешний: 1. **Бизнес-логика** (стандартная библиотека Go). -2. **Инструменты** (база данных, HTTP сервер, брокер сообщений, любые другие пакеты - и фреймворки). +2. **Инструменты** (база данных, HTTP сервер, брокер сообщений, любые другие пакеты +и фреймворки). **Внутренний** слой с бизнес-логикой должен быть чистым, то есть: - Не иметь в себе импортов пакетов из внешнего слоя. @@ -55,40 +53,45 @@ Dependency Inversion (тот самый из SOLID) — принцип инве **Внешний** слой имеет другие ограничения: - Все компоненты этого слоя не знают о существовании друг друга. -- Как из одного инструмента вызвать другой? Напрямую никак, только через - внутренний слой логики. +- Как из одного инструмента вызвать другой? Напрямую никак, только через +внутренний слой логики. - Обращения к внутреннему слою происходят через интерфейс(!). - Данные передаются в том формате, который удобен для бизнес-логики. **Например**, вам нужно обратиться из http хендлера к базе данных. И http, и -БД находятся во внешнем слое, значит они ничего не знают друг о друге. Связь +БД находятся во внешнем слое, значит они ничего не знают друг о друге. Связь между ними осуществляется через `service` (бизнес-логику): ``` HTTP (delivery) > service service > Postrges (repo) service < Postrges (repo) HTTP (delivery) < service -``` +``` Стрелочками > и < показано пересечение границ слоев через Интерфейсы. То же самое на картинке: -![Example](pkg/img/example-http-db.png) +![Example](docs/img/example-http-db.png) ### Терминология Чистой Архитектуры -- **Entities** — объекты, которыми оперирует бизнес-логика. В коде называются -по именам принятым в организации. Находятся в папке `internal/domain`. Domain намекает на то что мы придерживаемся - принципов DDD (domain driven design), это отчасти так, но без фанатизма. В терминах MVC entity это модели. Удобно - когда модели называются по именам предметной области. - +- **Entities** — объекты, которыми оперирует бизнес-логика. В коде называются +по именам принятым в организации. Находятся в папке `internal/domain`. Domain +намекает на то что мы придерживаемся принципов DDD (domain driven design), это +отчасти так, но без фанатизма. В терминах MVC entity это модели. Удобно когда +модели называются по именам предметной области. + -- **Use Cases** — это бизнес-логика, манипулирующая внешними пакетами через интерфейсы. -Находится в папке `internal/service`. Называть бизнес-логику словом _service_ не очень идеоматично с точки зрения Чистой - Архитектуры, но слово _service_ удобнее использовать для названия пакета, чем _usecase_. +- **Use Cases** — это бизнес-логика, манипулирующая внешними пакетами через +интерфейсы. Находится в папке `internal/service`. Называть бизнес-логику словом +_service_ не очень идеоматично с точки зрения Чистой Архитектуры, но слово +_service_ удобнее использовать для названия пакета, чем _usecase_. -Слой с которым непосредственно взаимодействует бизнес-логика обычно называют инфраструктурным слоем - _infrastructure_. -Это могут быть репозитории `internal/service/repo`, внешние webapi `internal/service/webapi`, любые pkg и другие -микросервисы. В шаблоне инфраструктурные пакеты находятся внутри каталога `internal/service`. +Слой с которым непосредственно взаимодействует бизнес-логика обычно называют +инфраструктурным слоем - _infrastructure_. Это могут быть репозитории +`internal/service/repo`, внешние webapi `internal/service/webapi`, любые pkg +и другие +микросервисы. В шаблоне инфраструктурные пакеты находятся внутри каталога +`internal/service`. Как называть точки входа, вопрос открыт. Варианты: - delivery @@ -101,11 +104,12 @@ Dependency Inversion (тот самый из SOLID) — принцип инве - primary ### Dependency Injection -Для того чтобы убрать зависимость бизнес-логики от внешних пакетов, используется инъекция зависимостей. +Для того чтобы убрать зависимость бизнес-логики от внешних пакетов, используется +инъекция зависимостей. -Через конструктор NewService мы делаем инъекцию зависимости в структуру бизнес-логики. Таким образом бизнес-логика -становится независимой (и даже переносимой). Мы можем подменить реализацию интерфейса и при этом не вносить правки в -пакет `service`. +Через конструктор NewService мы делаем инъекцию зависимости в структуру бизнес-логики. +Таким образом бизнес-логика становится независимой (и даже переносимой). Мы можем +подменить реализацию интерфейса и при этом не вносить правки в пакет `service`. ```go package service @@ -130,7 +134,8 @@ func (s *Service) Do() { s.repo.Get() } ``` -Это так же позволит нам делать автогенерацию моков (например с помощью библиотеки _mockery_) и легко писать юнит-тесты. +Это так же позволит нам делать автогенерацию моков (например с помощью библиотеки +_mockery_) и легко писать юнит-тесты. ### Дополнительные слои Классический вариант [Clean Architecture](https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html) @@ -144,18 +149,18 @@ func (s *Service) Do() { сложной логики. _______________________________ -Сложные инструменты из внешнего слоя рекомендуется делить на дополнительные -слои абстракции. Следует руководствоваться здравым смыслом и добавлять слои +Сложные инструменты из внешнего слоя рекомендуется делить на дополнительные +слои абстракции. Следует руководствоваться здравым смыслом и добавлять слои лишь в том случае, если это действительно необходимо. ### Альтернативные подходы -Кроме Чистой архитектуры есть очень близкие по духу — Луковая архитектура и -Гексагональная (Порты и адаптеры). В основе всех подходов лежит базовый принцип -инверсии зависимостей. Все подходы преследуют цель уменьшить зацепление и +Кроме Чистой архитектуры есть очень близкие по духу — Луковая архитектура и +Гексагональная (Порты и адаптеры). В основе всех подходов лежит базовый принцип +инверсии зависимостей. Все подходы преследуют цель уменьшить зацепление и разграничить ответственность. Полезные ссылки --------------- - [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/) +- [12 факторов](https://12factor.net/ru/) \ No newline at end of file diff --git a/cmd/app/main.go b/cmd/app/main.go index 79e137c1..46fb7470 100644 --- a/cmd/app/main.go +++ b/cmd/app/main.go @@ -23,10 +23,7 @@ func main() { zap := logger.NewZapLogger(cfg.Log.ZapLevel) defer zap.Close() - rollbar := logger.NewRollbarLogger(cfg.Log.RollbarToken, cfg.Log.RollbarEnv) - defer rollbar.Close() - - logger.NewAppLogger(zap, rollbar, cfg.App.Name, cfg.App.Version) + logger.NewAppLogger(zap, cfg.App.Name, cfg.App.Version) // Run app.Run(&cfg) diff --git a/config/config.go b/config/config.go index c6a822c2..d267e063 100644 --- a/config/config.go +++ b/config/config.go @@ -18,9 +18,7 @@ type HTTP struct { } type Log struct { - ZapLevel string `env-required:"true" yaml:"zap_level" env:"LOG_ZAP_LEVEL"` - RollbarEnv string `env-required:"true" yaml:"rollbar_env" env:"LOG_ROLLBAR_ENV"` - RollbarToken string `env-required:"true" env:"LOG_ROLLBAR_TOKEN"` + ZapLevel string `env-required:"true" yaml:"zap_level" env:"LOG_ZAP_LEVEL"` } type PG struct { diff --git a/docker-compose.yml b/docker-compose.yml index 81e2ebc3..48d51cbd 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -24,8 +24,6 @@ services: build: . container_name: app image: app - env_file: - - .env environment: PG_URL: 'postgres://user:pass@postgres:5432/postgres' RMQ_URL: 'amqp://guest:guest@rabbitmq:5672/' diff --git a/pkg/img/example-http-db.png b/docs/img/example-http-db.png similarity index 100% rename from pkg/img/example-http-db.png rename to docs/img/example-http-db.png diff --git a/pkg/img/layers.png b/docs/img/layers.png similarity index 100% rename from pkg/img/layers.png rename to docs/img/layers.png diff --git a/go.mod b/go.mod index de2976b5..652711d9 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,6 @@ require ( github.com/jackc/pgx/v4 v4.10.1 github.com/mattn/go-colorable v0.1.8 // indirect github.com/pkg/errors v0.9.1 - github.com/rollbar/rollbar-go v1.2.0 github.com/streadway/amqp v1.0.0 github.com/swaggo/files v0.0.0-20190704085106-630677cd5c14 github.com/swaggo/gin-swagger v1.3.0 diff --git a/go.sum b/go.sum index 4153451d..1d58f1c4 100644 --- a/go.sum +++ b/go.sum @@ -490,8 +490,6 @@ github.com/remyoudompheng/bigfft v0.0.0-20190728182440-6a916e37a237 h1:HQagqIiBm github.com/remyoudompheng/bigfft v0.0.0-20190728182440-6a916e37a237/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/rogpeppe/go-internal v1.3.0 h1:RR9dF3JtopPvtkroDZuVD7qquD0bnHlKSqaQhgwt8yk= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rollbar/rollbar-go v1.2.0 h1:CUanFtVu0sa3QZ/fBlgevdGQGLWaE3D4HxoVSQohDfo= -github.com/rollbar/rollbar-go v1.2.0/go.mod h1:czC86b8U4xdUH7W2C6gomi2jutLm8qK0OtrF5WMvpcc= github.com/rs/xid v1.2.1 h1:mhH9Nq+C1fY2l1XIpgxIiUOfNpRBYH1kKcr+qfKgjRc= github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= diff --git a/pkg/logger/logger.go b/pkg/logger/logger.go index c4b4bd81..01da6bd2 100644 --- a/pkg/logger/logger.go +++ b/pkg/logger/logger.go @@ -1,13 +1,19 @@ package logger +import ( + "fmt" + + "go.uber.org/zap" +) + var appLogger Logger //nolint:gochecknoglobals // it's necessary -func NewAppLogger(zap *ZapLogger, rollbar *RollbarLogger, appName, appVersion string) { +func NewAppLogger(zapLogger *ZapLogger, appName, appVersion string) { fields := []Field{ {"app-name", appName}, {"app-version", appVersion}, } - appLogger = &loggers{zap, rollbar, fields} + appLogger = &logger{zapLogger, fields} } func Debug(msg string, fields ...Field) { @@ -29,3 +35,46 @@ func Error(err error, msg string, fields ...Field) { func Fatal(err error, msg string, fields ...Field) { appLogger.fatal(err, msg, fields...) } + +type logger struct { + zap *ZapLogger + defaultFields []Field +} + +func (l *logger) debug(msg string, fields ...Field) { + fields = append(l.defaultFields, fields...) + l.zap.Debug(msg, zapFields(fields)...) +} + +func (l *logger) info(msg string, fields ...Field) { + fields = append(l.defaultFields, fields...) + l.zap.Info(msg, zapFields(fields)...) +} + +func (l *logger) warn(msg string, fields ...Field) { + fields = append(l.defaultFields, fields...) + l.zap.Warn(msg, zapFields(fields)...) +} + +func (l *logger) error(err error, msg string, fields ...Field) { + err = fmt.Errorf("%s: %w", msg, err) + + fields = append(l.defaultFields, fields...) + l.zap.Error(err.Error(), zapFields(fields)...) +} + +func (l *logger) fatal(err error, msg string, fields ...Field) { + err = fmt.Errorf("%s: %w", msg, err) + + fields = append(l.defaultFields, fields...) + l.zap.Fatal(err.Error(), zapFields(fields)...) // os.Exit() +} + +func zapFields(fields []Field) []zap.Field { + s := make([]zap.Field, 0, len(fields)) + for _, field := range fields { + s = append(s, zap.Reflect(field.Key, field.Val)) + } + + return s +} diff --git a/pkg/logger/loggers.go b/pkg/logger/loggers.go deleted file mode 100644 index 0200e5b4..00000000 --- a/pkg/logger/loggers.go +++ /dev/null @@ -1,65 +0,0 @@ -package logger - -import ( - "fmt" - - "github.com/rollbar/rollbar-go" - "go.uber.org/zap" -) - -type loggers struct { - zap *ZapLogger - rollbar *RollbarLogger - defaultFields []Field -} - -func (l *loggers) debug(msg string, fields ...Field) { - fields = append(l.defaultFields, fields...) - l.zap.Debug(msg, zapFields(fields)...) -} - -func (l *loggers) info(msg string, fields ...Field) { - fields = append(l.defaultFields, fields...) - l.zap.Info(msg, zapFields(fields)...) -} - -func (l *loggers) warn(msg string, fields ...Field) { - fields = append(l.defaultFields, fields...) - l.rollbar.MessageWithExtras(rollbar.WARN, msg, rollbarMap(fields)) - l.zap.Warn(msg, zapFields(fields)...) -} - -func (l *loggers) error(err error, msg string, fields ...Field) { - err = fmt.Errorf("%s: %w", msg, err) - - fields = append(l.defaultFields, fields...) - l.rollbar.ErrorWithStackSkipWithExtras(rollbar.ERR, err, 3, rollbarMap(fields)) - l.zap.Error(err.Error(), zapFields(fields)...) -} - -func (l *loggers) fatal(err error, msg string, fields ...Field) { - err = fmt.Errorf("%s: %w", msg, err) - - fields = append(l.defaultFields, fields...) - l.rollbar.ErrorWithStackSkipWithExtras(rollbar.CRIT, err, 3, rollbarMap(fields)) - l.rollbar.Close() - l.zap.Fatal(err.Error(), zapFields(fields)...) // os.Exit() -} - -func rollbarMap(fields []Field) map[string]interface{} { - m := make(map[string]interface{}, len(fields)*2) //nolint:gomnd // fields number always 2 - for _, field := range fields { - m[field.Key] = field.Val - } - - return m -} - -func zapFields(fields []Field) []zap.Field { - s := make([]zap.Field, 0, len(fields)) - for _, field := range fields { - s = append(s, zap.Reflect(field.Key, field.Val)) - } - - return s -} diff --git a/pkg/logger/rollbar.go b/pkg/logger/rollbar.go deleted file mode 100644 index b21226c9..00000000 --- a/pkg/logger/rollbar.go +++ /dev/null @@ -1,17 +0,0 @@ -package logger - -import "github.com/rollbar/rollbar-go" - -type RollbarLogger struct { - *rollbar.Client -} - -func NewRollbarLogger(token, env string) *RollbarLogger { - client := rollbar.NewAsync(token, env, "", "", "") - - return &RollbarLogger{client} -} - -func (r *RollbarLogger) Close() { - r.Client.Wait() -}