Skip to content
1 change: 1 addition & 0 deletions cmd/agent/main_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
package main
26 changes: 25 additions & 1 deletion cmd/server/main.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,27 @@
// Server for metrics collection and alerting service
package main

func main() {}
import (
"log"

"github.com/srg-bnd/observator/internal/handlers"
"github.com/srg-bnd/observator/internal/server"
"github.com/srg-bnd/observator/internal/services"
"github.com/srg-bnd/observator/internal/storage"
)

var memStorage storage.MemStorage

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

И еще тут напишу, вместо глобальных переменных и init() лучше использовать внедрение зависимостей. Вот так примерно
Как обещал на 1:1 вот код

type App struct {
    storage *storage.MemStorage
    server  *server.Server
}

func NewApp() *App {
    storage := storage.NewMemStorage()
    return &App{
        storage: storage,
        server: server.NewServer(storage),
    }
}


func init() {
memStorage = storage.NewMemStorage()
// HACK to acess memStorage
server.MemStorage = &memStorage
handlers.MemStorage = &memStorage
services.MemStorage = &memStorage
}

func main() {
if err := server.Start(); err != nil {
log.Fatal(err)
}
}
1 change: 1 addition & 0 deletions cmd/server/main_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
package main
Empty file added internal/agent/.keep
Empty file.
33 changes: 33 additions & 0 deletions internal/handlers/handlers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Handlers for server
package handlers

import (
"net/http"

"github.com/srg-bnd/observator/internal/services"
"github.com/srg-bnd/observator/internal/storage"
)

var (

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

MemStorage *storage.MemStorage
)

func UpdateMetricHandler(res http.ResponseWriter, req *http.Request) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UpdateMetricHandler содержит слишком много логики. Лучше разделить на валидацию и обработку. Типа такого:

func (h *Handler) UpdateMetricHandler(w http.ResponseWriter, r *http.Request) {
    metric, err := h.parseAndValidateMetric(r)
    if err != nil {
        h.handleError(w, err)
        return
    }
    h.processMetric(w, metric)
}

metricType := req.PathValue("metricType")
metricName := req.PathValue("metricName")
metricValue := req.PathValue("metricValue")

data := services.UpdateMetricService(metricType, metricName, metricValue)

if data.Ok {
res.Header().Set("content-type", "text/plain; charset=utf-8")
res.WriteHeader(http.StatusOK)
} else {
switch data.Status {
case "typeError", "valueError":
res.WriteHeader(http.StatusBadRequest)
case "nameError":
res.WriteHeader(http.StatusNotFound)
}
}
}
9 changes: 9 additions & 0 deletions internal/handlers/handlers_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package handlers

import (
"testing"
)

func TestUpdateMetricHandler(t *testing.T) {
// TODO
}
24 changes: 24 additions & 0 deletions internal/middleware/middleware.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// Middleware for server
package middleware

import "net/http"

type Middleware func(http.Handler) http.Handler

func Conveyor(h http.Handler, middlewares ...Middleware) http.Handler {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Переименуй в Chain, так  обычно это делают в гоу

for _, middleware := range middlewares {
h = middleware(h)
}
return h
}

func CheckMethodPost(next http.Handler) http.Handler {
return http.HandlerFunc(func(resWriter http.ResponseWriter, req *http.Request) {
if req.Method != http.MethodPost {
http.Error(resWriter, "Method not allowed", http.StatusMethodNotAllowed)
return
}

next.ServeHTTP(resWriter, req)
})
}
1 change: 1 addition & 0 deletions internal/middleware/middleware_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
package middleware
46 changes: 46 additions & 0 deletions internal/server/server.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package server

import (
"net/http"

"github.com/srg-bnd/observator/internal/handlers"
"github.com/srg-bnd/observator/internal/middleware"
"github.com/srg-bnd/observator/internal/storage"
)

const (
defaultHost = `:8080`
)

var (
MemStorage *storage.MemStorage

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

И тут еще, нарушен принцип инверсии зависимостей - сервер не должен знать о конкретной реализации хранилища

mux *http.ServeMux
)

func init() {
// Routes
mux = http.NewServeMux()
mux.Handle(
`/update/{metricType}/{metricName}/{metricValue}`,
middleware.Conveyor(
http.HandlerFunc(handlers.UpdateMetricHandler),
middleware.CheckMethodPost,
))
}

// Init server dependencies before startup
func Start() error {
host, err := getHost()
if err != nil {
return err
}

http.ListenAndServe(host, mux)

return nil
}

// Selects the server host
func getHost() (string, error) {
return defaultHost, nil
}
18 changes: 18 additions & 0 deletions internal/server/server_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package server

import (
"testing"
)

func TestStart(t *testing.T) {
// TODO
}

func TestGetHost(t *testing.T) {
got, _ := getHost()
want := ":8080"

if got != want {
t.Errorf(`Incorrect 'getHost()' method behavior, got "%v", want "%v"`, got, want)
}
}
61 changes: 61 additions & 0 deletions internal/services/services.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// Handlers for server
package services

import (
"slices"
"strconv"

"github.com/srg-bnd/observator/internal/storage"
)

type Data struct {
Ok bool
Status string
}

func successData(status string) *Data {
return &Data{Ok: true, Status: status}
}

func errorData(status string) *Data {
return &Data{Ok: false, Status: status}
}

var (
MemStorage *storage.MemStorage
)

// Services

func UpdateMetricService(metricType, metricName, metricValue string) *Data {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

И вот тут еще у тебя, слой содержит бизнес-логику смешанную с форматированием данных, надо разделить ответственность

// Check type
if !slices.Contains([]string{"counter", "gauge"}, metricType) {
return errorData("typeError")
}

// Check name
if metricName == "" {
return errorData("nameError")
}

switch metricType {
case "counter":
// Check and set value
value, err := strconv.ParseInt(metricValue, 10, 64)
if err != nil {
return errorData("valueError")
} else {
MemStorage.SetCounter(metricName, value)
}
case "gauge":
// Check and set value
value, err := strconv.ParseFloat(metricValue, 64)
if err != nil {
return errorData("valueError")
} else {
MemStorage.SetGauge(metricName, value)
}
}

return successData("Ok")
}
9 changes: 9 additions & 0 deletions internal/services/services_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package services

import (
"testing"
)

func TestUpdateMetricService(t *testing.T) {
// TODO
}
42 changes: 42 additions & 0 deletions internal/storage/storage.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// Storage for metrics
package storage

type (
gauge float64
counter int64
)

type MemStorage struct {
gauges map[string]gauge
counters map[string]counter
}

// Create MemStorage instance
func NewMemStorage() MemStorage {
return MemStorage{
gauges: make(map[string]gauge),
counters: make(map[string]counter),
}
}

// Change gauge by key
func (mStore *MemStorage) SetGauge(key string, value float64) error {
mStore.gauges[key] = gauge(value)
return nil
}

// Return gauge by key
func (mStore *MemStorage) GetGauge(key string) float64 {
return float64(mStore.gauges[key])
}

// Change counter by key
func (mStore *MemStorage) SetCounter(key string, value int64) error {
mStore.counters[key] += counter(value)
return nil
}

// Return gauge by counter
func (mStore *MemStorage) GetCounter(key string) int64 {
return int64(mStore.counters[key])
}
60 changes: 60 additions & 0 deletions internal/storage/storage_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package storage

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Отличное покрытие тестами для пакета


import (
"reflect"
"testing"
)

func TestNewMemStorage(t *testing.T) {
got := NewMemStorage()
want := MemStorage{}

if reflect.TypeOf(want) != reflect.TypeOf(got) {
t.Errorf(`Incorrect structure type for NewMemStorage(), got "%v", want "%v"`, got, want)
}
}

func TestSetGauge(t *testing.T) {
memStore := NewMemStorage()
want := float64(1)
memStore.SetGauge("key", want)
got := float64(memStore.gauges["key"])

if got != want {
t.Errorf(`Incorrect 'SetGauge("key", %#v)' method behavior, got "%v", want "%v"`, want, got, want)
}
}

func TestGetGauge(t *testing.T) {
memStore := NewMemStorage()
want := float64(1)
memStore.gauges["key"] = gauge(want)
got := memStore.GetGauge("key")

if want != float64(got) {
t.Errorf(`Incorrect 'GetGauge("key")' method behavior, got "%v", want "%v"`, got, want)
}
}

func TestSetCounter(t *testing.T) {
memStore := NewMemStorage()
counter := int64(10)
want := int64(memStore.counters["key"]) + counter
memStore.SetCounter("key", counter)
got := int64(memStore.counters["key"])

if got != want {
t.Errorf(`Incorrect 'SetCounter("key", %#v)' method behavior, got "%v", want "%v"`, want, got, want)
}
}

func TestGetCounter(t *testing.T) {
memStore := NewMemStorage()
want := int64(1)
memStore.counters["key"] = counter(want)
got := int64(memStore.GetCounter("key"))

if want != got {
t.Errorf(`Incorrect 'GetCounter("key")' method behavior, got "%v", want "%v"`, got, want)
}
}