you_are_enot
@you_are_enot
Пограммист любитель

Как структурировать http сервер?

Я использую пакет gorilla/mux и столкнулся с проблемой, что всю логику приходится размещать в одном файле. Сколько не искал, не могу найти пример того, как правильно разделять приложение.

Приведу пример.
Файл server.go. В нём описывается тип server и находятся функция создания нового сервера и все обработчики, мидлвееры.

type server struct {
	router       *mux.Router
	logger       *logrus.Logger
	store        store.Store
	sessionStore sessions.Store
}

func newServer(store store.Store, sessionStore sessions.Store) *server {
	s := &server{
		router:       mux.NewRouter(),
		logger:       logrus.New(),
		store:        store,
		sessionStore: sessionStore,
	}

	s.configureRouter()

	return s
}

func (s *server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	s.router.ServeHTTP(w, r)
}


Конфигурирование роутера.
func (s *server) configureRouter() {
	s.router.Use(s.setRequestID)
	s.router.Use(s.logRequest)
	s.router.Use(handlers.CORS(handlers.AllowedOrigins([]string{"*"})))
	s.router.HandleFunc("/users", s.handleUsersCreate()).Methods("POST")
	s.router.HandleFunc("/sessions", s.handleSessionCreate()).Methods("POST")

	private := s.router.PathPrefix("/private").Subrouter()
	private.Use(s.authenticateUser)
	private.HandleFunc("/whoami", s.handleWhoami()).Methods("GET")
}


Пример обработчика.
func (s *server) handleWhoami() http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		s.respond(w, r, http.StatusOK, r.Context().Value(ctxKeyUser).(*model.User))
	}
}


Я не могу вынести обработки в отдельный файл, потому-что это, как мне кажется, приведёт к циклическим импортам, а писать в одном файле не очень удобно. Хочется отделить код сервера от бизнес логики.
  • Вопрос задан
  • 241 просмотр
Пригласить эксперта
Ответы на вопрос 1
WinPooh32
@WinPooh32
Stack Overflow answer searching expert
Я использую такой подход:
1) Хендлеры занимаются лишь обработкой входных параметров от клиента, например, извлечение параметров, вывод ошибок пользователю;
2) Бизнес-логика вынесена в отдельный пакет или класс;
3) Запросы во внешние хранилища тоже в отдельном пакете;
4) Передача соединений бд или хранилищ только через контекст с помощью миддлвари.

Пример структуры проекта:
| + api
| - - router.go
| + models
| - - modelA.go
| - - modelB.go
| + app
| - - taskA.go
| - - taskB.go
| + database
| - - requests.go
| main.go

main.go:
package main
import "api"
func main(){
  ...
  r := api.NewRouter(...)
  http.ListenAndServe(...)
}


api/router.go:
package api
import (
  "app"
  "models"
)
func NewRouter(...) ...{
  // Инициализация роутера, регистрация обработчиков и т.д.
  ...
  return r
}

func handleEndpointA(w http.ResponseWriter, r *http.Request){
  // Извлечение, валидация параметров.
  // Извлечение соединений БД и друхих хранилищ из контекста.
  // Обработка ошибок.
  ...
  result, err := app.DoTaskA(ctx, db, paramA, paramB)
  ...
  render(w, result)
}

func handleEndpointB(w http.ResponseWriter, r *http.Request){
  ...
  result, err := app.DoTaskB(ctx, db, paramC)
  ...
  render(w, result)
}


app/taskA.go:
package app
import(
    "database"
    "models"
)
func DoTaskA(ctx, db, paramA, paramB) (result, err){
  // Какие-то манипуляции с параметрами.
  ...
  // Запрос в бд.
  modelA, err := database.RequestA(ctx, db, param)
  ...
  return result, err
}


database/requests.go:
package database
import (
  "models"
)
func RequestA(ctx, db, param) (result, err) {
  // Не забываем поставить таймаут на запрос к БД.
  ctx, cancel = context.WithTimeout(ctx, defaultRequestTimeout)
  defer cancel()
  ...
  result, err := db.DoRequest(ctx, ...)
  ...
  return result, err
}
Ответ написан
Комментировать
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы