Skip to content

Commit

Permalink
Iter13 (#16)
Browse files Browse the repository at this point in the history
* рефакторинг агента по итогам встречи с ментором

* рефакторинг сервера по результам встречи с ментором

* реализовал инкремент 6

* исправления по результатам работы статического анализатора

* реализовал iter7

* pool->poll

* обновление в chi

* исправление по результатам статического анализатора

* ещё исправления по статическому анализатору

* поправил обновление метрик

* убрал лишние проверки, из-за которых не проходили значения спорядком (типа 1.8e+7)

* перевел агента на json

* реализовал компрессию на сервере при передаче ответов

* incr8

* поправил названия

* обновил чтение конфигурации для сервера

* добавил методы сохранения и восстановления хранилища

* наработки

* наработки (не собирается)

* рефакторинг сервера, тесты пройдены

* наработки по инкременту 9

* небольшой комментарий

* ещё рефакторинг

* наработки по инкременту 9

* изменения по результам работы статического анализатора
+ доп вопросы
+ немного комментариев

* накидал идею по архитектуре

* наработки по gomock

* реализовал adapter для ping

* небольшой рефакторинг, логирование + postgres

* поправил имя

* сделал memStorage и pgStorage заменяемыми

* iter11

* добавил проверку rows.Err()

* переформулировка гексогональной архитектуры с добавлением usecase

* Упроситл конфигурацию сервера и вынес в cmd.

* реализовал сервер

* поправил агент, поправил ошибку в сервере

* вопрос про docker

* актуализировал вопросы

* добавил функцию для обработки ошибок

* реализовал обработку ошибок

* Добавил обработку ошибки соединения в agent

* инкремент 13

* всякая мелочевка

* переименовал пару пакетов; заменил самописные заглушки на gomock

* закрыл Body

* небольшие правки после ревью второго спринта

* актуализировал вопросы

* добавил все же ReadAll

* ещё вопрос
  • Loading branch information
StasMerzlyakov authored Mar 20, 2024
1 parent 5a46c41 commit 88e562a
Show file tree
Hide file tree
Showing 69 changed files with 5,184 additions and 1,261 deletions.
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
*.png filter=lfs diff=lfs merge=lfs -text
23 changes: 23 additions & 0 deletions Questions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
- АРХИТЕКТУРА!!!
- убрал картинку - после нескольких итераций пришел к гексогональной архитектуре но с use-кейсами и доменной областью, как средством деления логики приложения
в учебном приложении получилиcь области
- работа с бэкапом
- администривные операции(пока только ping)
- работа с метриками


- storage/wrapper/retriable.go - может есть более изящное решение? (дублирование кода, создание функциональные оберток)

- middleware/testutils/http_handler.go - добавил Handler интерфейс ради генерации mock. Можно ли как-то заставить gomock сгенерировать mock для стандартного интерфейса http.Handler.

- defer req.Body.Close() в http-хэндлере:
В документации (https://pkg.go.dev/net/http#Request):
The Server will close the request body. The ServeHTTP Handler does not need to.
Получается что можно не заморачиваться?

- DOCKER - было бы логично добавить тестирование storage на postgres, но смущает реализация unit-тестов и для локального запуска и в связке с github metricstest. (появляются вопросы по реализации - можно ли завязаться на переменные окружение POSTGRES_PASSWORD: postgres, POSTGRES_DB: praktikum и т.д). Возможно будет использование docker в кусре далее? Стоит ли сейчас заниматься?

- (ОТВЕТ ПОЛУЧЕН; TODO) w.WriteHeader(http.StatusOK) - где писать, надо ли вообще писать? TODO поправить хэндлеры

- (ОТВЕТ ПОЛУЧЕН; TODO ) Загрузка конфигурации (flag, env) (см internal/config/server.go) - можно ли покрыть тестами (ссылка на пример, если есть; надо ли вообще? - TODO пример - https://github.com/golang/go/blob/master/src/flag/example_test.go)

34 changes: 25 additions & 9 deletions cmd/agent/agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,25 +8,41 @@ import (
"syscall"

"github.com/StasMerzlyakov/go-metrics/internal/agent"
"github.com/StasMerzlyakov/go-metrics/internal/common/wrapper/retriable"
"github.com/StasMerzlyakov/go-metrics/internal/config"
)

type Agent interface {
Start(ctx context.Context)
Wait()
}

func main() {
agentCfg, err := agent.LoadConfig()
agentCfg, err := config.LoadAgentConfig()
if err != nil {
log.Fatal(err)
}

metricStorage := agent.NewMemStatsStorage()
resultSender := agent.NewHTTPResultSender(agentCfg.ServerAddr)
retryCfg := retriable.DefaultConf(syscall.ECONNREFUSED)
retryableResultSender := agent.NewHTTPRetryableResultSender(*retryCfg, resultSender)

var agnt Agent = agent.Create(agentCfg,
retryableResultSender,
metricStorage,
)

// Взято отсюда: "Реализация Graceful Shutdown в Go"(https://habr.com/ru/articles/771626/)
// Сейчас выглядит избыточным - оставил как задел на будущее для сервера
ctx, cancel := context.WithCancel(context.Background())
ctx, cancelFn := context.WithCancel(context.Background())
exit := make(chan os.Signal, 1)
signal.Notify(exit, os.Interrupt, syscall.SIGTERM)

if agent, err := agent.CreateAgent(ctx, agentCfg); err != nil {
panic(err)
} else {
<-exit
cancel()
agent.Wait() // ожидаение завершения go-рутин в агенте
}
agnt.Start(ctx)
defer func() {
cancelFn()
agnt.Wait()
}()
<-exit
}
179 changes: 174 additions & 5 deletions cmd/server/server.go
Original file line number Diff line number Diff line change
@@ -1,19 +1,188 @@
package main

import (
"log"
"context"
"errors"
"net/http"
"os"
"os/signal"
"syscall"
"time"

"github.com/StasMerzlyakov/go-metrics/internal/server"
"github.com/StasMerzlyakov/go-metrics/internal/common/wrapper/retriable"
"github.com/StasMerzlyakov/go-metrics/internal/config"
"github.com/StasMerzlyakov/go-metrics/internal/server/adapter/fs/backup"
"github.com/StasMerzlyakov/go-metrics/internal/server/adapter/http/handler"
"github.com/StasMerzlyakov/go-metrics/internal/server/adapter/http/middleware"
"github.com/StasMerzlyakov/go-metrics/internal/server/adapter/http/middleware/compress"
"github.com/StasMerzlyakov/go-metrics/internal/server/adapter/http/middleware/logging"
"github.com/StasMerzlyakov/go-metrics/internal/server/adapter/storage/memory"
"github.com/StasMerzlyakov/go-metrics/internal/server/adapter/storage/postgres"
"github.com/StasMerzlyakov/go-metrics/internal/server/adapter/storage/wrapper"
"github.com/StasMerzlyakov/go-metrics/internal/server/app"
"github.com/StasMerzlyakov/go-metrics/internal/server/domain"
"github.com/go-chi/chi/v5"
"github.com/jackc/pgerrcode"
"github.com/jackc/pgx/v5/pgconn"

"go.uber.org/zap"
)

type Server interface {
Start(ctx context.Context)
Shutdown(ctx context.Context)
}

func createMiddleWareList(log *zap.SugaredLogger) []func(http.Handler) http.Handler {
return []func(http.Handler) http.Handler{
logging.NewLoggingResponseMW(log),
compress.NewCompressGZIPResponseMW(log), //compress.NewCompressGZIPBufferResponseMW(log),
compress.NewUncompressGZIPRequestMW(log),
logging.NewLoggingRequestMW(log),
}
}

type FullStorage interface {
app.Storage
app.Pinger
Bootstrap(ctx context.Context) error
Close(ctx context.Context) error
}

func main() {

srvConf, err := server.LoadConfig()
// -------- Контекст сервера ---------
srvCtx, cancelFn := context.WithCancel(context.Background())

// -------- Конфигурация ----------
srvConf, err := config.LoadServerConfig()
if err != nil {
panic(err)
}

// -------- Логгер ---------------
logger, err := zap.NewDevelopment()
if err != nil {
log.Fatal(err)
// вызываем панику, если ошибка
panic("cannot initialize zap")
}
defer logger.Sync()

sugarLog := logger.Sugar()

// -------- Хранилище -------------
var storage FullStorage

if srvConf.DatabaseDSN != "" {
storage = postgres.NewStorage(sugarLog, srvConf.DatabaseDSN)

// при работе с Postgres добавляем retriable-обертку

// функция, обрабатывающая ошибки; в нужных случаях выкидывает нужную ошибку(domain.ErrDBConnection)
// на которую реагирует retriableWrapper
pgErrPreProcFn := func(err error) error {
if err == nil {
return nil
}
var pgErr *pgconn.PgError
if errors.As(err, &pgErr) {
if pgerrcode.IsConnectionException(pgErr.Code) {
return domain.ErrDBConnection
}
}

var conErr *pgconn.ConnectError
if errors.As(err, &conErr) {
return domain.ErrDBConnection
}
return err
}

retriableConf := retriable.DefaultConfFn(domain.ErrDBConnection, pgErrPreProcFn)
storage = wrapper.NewRetriable(retriableConf, sugarLog, storage)

if err := server.CreateServer(srvConf); err != nil {
} else {
storage = memory.NewStorage()
}

defer storage.Close(srvCtx)

if err := storage.Bootstrap(srvCtx); err != nil {
panic(err)
}

// -------- Бэкап ------------
backupFomratter := backup.NewJSON(sugarLog, srvConf.FileStoragePath)
backUper := app.NewBackup(sugarLog, storage, backupFomratter)

if srvConf.Restore {
// восстановленгие бэкапа
if err := backUper.RestoreBackUp(srvCtx); err != nil {
panic(err)
}
}

// проверяем - нужен ли синхронный бэкап
doSyncBackup := srvConf.StoreInterval == 0

if !doSyncBackup {
// запускаем фоновый процесс
go func() {
storeInterval := time.Duration(srvConf.StoreInterval) * time.Second
var ticker = time.NewTicker(storeInterval)
defer ticker.Stop()
for {
select {
case <-srvCtx.Done():
sugarLog.Infow("Run", "msg", "backup finished")
return
case <-ticker.C:
if err := backUper.DoBackUp(srvCtx); err != nil {
sugarLog.Fatalw("DoBackUp", "msg", err.Error())
}
}
}
}()

}

// ---------- Http сервер -----------
httpHandler := chi.NewMux()

// мидлы
mwList := createMiddleWareList(sugarLog)
middleware.Add(httpHandler, mwList...)

// операции с метриками
metricApp := app.NewMetrics(storage)
handler.AddMetricOperations(httpHandler, metricApp, sugarLog)

// административные операции
adminApp := app.NewAdminApp(sugarLog, storage)
handler.AddAdminOperations(httpHandler, adminApp, sugarLog)

// запускаем http-сервер
srv := &http.Server{
Addr: srvConf.URL,
Handler: httpHandler,
ReadTimeout: 0,
IdleTimeout: 0,
}

go func() {
if err := srv.ListenAndServe(); err != http.ErrServerClosed {
sugarLog.Fatalw("ListenAndServe", "msg", err.Error())
panic(err)
}
}()

// --------------- Обрабатываем остановку сервера --------------
exit := make(chan os.Signal, 1)
signal.Notify(exit, os.Interrupt, syscall.SIGTERM)

defer func() {
cancelFn()
srv.Shutdown(srvCtx)
}()
<-exit
}
29 changes: 24 additions & 5 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,31 @@ module github.com/StasMerzlyakov/go-metrics
go 1.19

require (
github.com/caarlos0/env v3.5.0+incompatible // indirect
github.com/caarlos0/env v3.5.0+incompatible
github.com/go-chi/chi/v5 v5.0.11
github.com/go-resty/resty/v2 v2.11.0
github.com/golang/mock v1.6.0
github.com/jackc/pgerrcode v0.0.0-20220416144525-469b46aa5efa
github.com/jackc/pgx/v5 v5.5.4
github.com/pkg/errors v0.9.1
github.com/sirupsen/logrus v1.9.3
github.com/stretchr/testify v1.8.4
github.com/ungerik/go-pool v0.0.0-20140720100922-d102a2c7872a
go.uber.org/zap v1.27.0
)

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/go-chi/chi/v5 v5.0.11 // indirect
github.com/go-resty/resty/v2 v2.11.0 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
github.com/jackc/puddle/v2 v2.2.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/stretchr/testify v1.8.4 // indirect
golang.org/x/net v0.17.0 // indirect
github.com/rogpeppe/go-internal v1.12.0 // indirect
go.uber.org/multierr v1.10.0 // indirect
golang.org/x/crypto v0.21.0 // indirect
golang.org/x/net v0.22.0 // indirect
golang.org/x/sync v0.1.0 // indirect
golang.org/x/sys v0.18.0 // indirect
golang.org/x/text v0.14.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
Loading

0 comments on commit 88e562a

Please sign in to comment.