Разбить код на слои

Код-ревьюMiddle30 минпроверка — Спринт13-t002

Дан сервис оформления заказа: HTTP-обработка, бизнес-логика (проверка остатка, оптовая скидка) и работа с БД смешаны в одном хендлере. Найдите проблемы и разнесите код по слоям, проговаривая решения: что куда выносится и почему исходный вариант плох.

Вопросы по ходу:

  • Зачем репозиторий прятать за интерфейсом?
  • На каком слое логировать ошибки?
  • Как покрыть такой код тестами?

Как на собесе: 30 минут, ищи всё — гонки, утечки, ошибки, безопасность, стиль. Ссылайся на строки.

review.gogo
1package main2 3import (4	"database/sql"5	"encoding/json"6	"fmt"7	"log"8	"net/http"9	"time"10)11 12var db *sql.DB13 14type Order struct {15	ID        int64   `json:"id"`16	UserID    int64   `json:"user_id"`17	ProductID int64   `json:"product_id"`18	Qty       int     `json:"qty"`19	Total     float64 `json:"total"`20}21 22func createOrder(w http.ResponseWriter, r *http.Request) {23	var o Order24	json.NewDecoder(r.Body).Decode(&o)25 26	var price float6427	var stock int28	row := db.QueryRow(fmt.Sprintf("SELECT price, stock FROM products WHERE id = %d", o.ProductID))29	if err := row.Scan(&price, &stock); err != nil {30		log.Println(err)31		w.Write([]byte("error"))32		return33	}34 35	if stock < o.Qty {36		w.WriteHeader(http.StatusInternalServerError)37		w.Write([]byte("not enough stock"))38		return39	}40 41	o.Total = price * float64(o.Qty)42	if o.Qty >= 10 {43		o.Total = o.Total * 0.944	}45 46	db.Exec(fmt.Sprintf("UPDATE products SET stock = stock - %d WHERE id = %d", o.Qty, o.ProductID))47	res, err := db.Exec(fmt.Sprintf(48		"INSERT INTO orders (user_id, product_id, qty, total, created_at) VALUES (%d, %d, %d, %f, '%s')",49		o.UserID, o.ProductID, o.Qty, o.Total, time.Now().Format(time.RFC3339)))50	if err != nil {51		log.Println(err)52		http.Error(w, err.Error(), 500)53		return54	}55	o.ID, _ = res.LastInsertId()56 57	json.NewEncoder(w).Encode(o)58}59 60func main() {61	db, _ = sql.Open("postgres", "postgres://admin:admin@localhost/shop")62	http.HandleFunc("/orders", createOrder)63	http.ListenAndServe(":8080", nil)64}
⌘ Enter — проверить