Skip to content

Commit

Permalink
Merge pull request #24 from evrone/develop
Browse files Browse the repository at this point in the history
Edited the readme file and removed the Rollbar logger
  • Loading branch information
golang-school authored Apr 2, 2021
2 parents df6e25c + 785d000 commit 46ae5d6
Show file tree
Hide file tree
Showing 16 changed files with 114 additions and 148 deletions.
5 changes: 0 additions & 5 deletions .env

This file was deleted.

1 change: 0 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
DISABLE_SWAGGER_HTTP_HANDLER=true
GIN_MODE=release
LOG_ROLLBAR_TOKEN=<TOKEN>
PG_URL=postgres://user:pass@localhost:5432/postgres
RMQ_URL=amqp://guest:guest@localhost:5672/
11 changes: 0 additions & 11 deletions Dockerfile.prod

This file was deleted.

21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2021 Evrone <mail@evrone.com>

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.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
include .env
include .env.example
export

compose-up:
Expand Down
73 changes: 39 additions & 34 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 сервер, брокер сообщений, любые другие пакеты
и фреймворки).

**Внутренний** слой с бизнес-логикой должен быть чистым, то есть:
- Не иметь в себе импортов пакетов из внешнего слоя.
Expand All @@ -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
Expand All @@ -101,11 +104,12 @@ Dependency Inversion (тот самый из SOLID) — принцип инве
- primary

### Dependency Injection
Для того чтобы убрать зависимость бизнес-логики от внешних пакетов, используется инъекция зависимостей.
Для того чтобы убрать зависимость бизнес-логики от внешних пакетов, используется
инъекция зависимостей.

Через конструктор NewService мы делаем инъекцию зависимости в структуру бизнес-логики. Таким образом бизнес-логика
становится независимой (и даже переносимой). Мы можем подменить реализацию интерфейса и при этом не вносить правки в
пакет `service`.
Через конструктор NewService мы делаем инъекцию зависимости в структуру бизнес-логики.
Таким образом бизнес-логика становится независимой (и даже переносимой). Мы можем
подменить реализацию интерфейса и при этом не вносить правки в пакет `service`.

```go
package service
Expand All @@ -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)
Expand All @@ -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/)
5 changes: 1 addition & 4 deletions cmd/app/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
4 changes: 1 addition & 3 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
2 changes: 0 additions & 2 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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/'
Expand Down
File renamed without changes
File renamed without changes
1 change: 0 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 0 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand Down
53 changes: 51 additions & 2 deletions pkg/logger/logger.go
Original file line number Diff line number Diff line change
@@ -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) {
Expand All @@ -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
}
65 changes: 0 additions & 65 deletions pkg/logger/loggers.go

This file was deleted.

17 changes: 0 additions & 17 deletions pkg/logger/rollbar.go

This file was deleted.

0 comments on commit 46ae5d6

Please sign in to comment.