Перейти веб-приложения с макета пользовательского хранилища


Я новичок, чтобы пойти и пытаются создать структуру веб-приложения. Я читал о чистой архитектуре и Бен Джонсон в блоге о пакет макет. Теперь я хочу поставить все это вместе. Это просто царапина. Абстракция сервис выглядит избыточным, но в реальном проекте будет услуг, который содержит более одного репозитория. Каково Ваше мнение в структурировании такой? И как я инициализировать ее вместе.

import (
    "fmt"
    "html/template"
    "log"
    "net/http"
    "strconv"
)

type user struct {
    name string
}

type userRepository interface {
    getByID(id int) (*user, error)
}

type userService struct {
    userRepository userRepository
}

func (us *userService) findUser(id int) (*user, error) {
    return us.userRepository.getByID(id)
}

type mockUserRepo struct{}

func (mr *mockUserRepo) getByID(id int) (*user, error) {
    return &user{"John Doe"}, nil
}

type safeHandlerFunc func(http.ResponseWriter, *http.Request) error

type mainHandler struct {
    //session
    //logger
    view *template.Template
}

func (h *mainHandler) handle(sh safeHandlerFunc) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("x-custom-header", "random")
        if err := sh(w, r); err != nil {
            //return some error view
            //log error
            http.Error(w, err.Error(), http.StatusInternalServerError)
            return
        }
    }
}

type userHandler struct {
    *mainHandler
    userService *userService
}

func (uh *userHandler) getUser(w http.ResponseWriter, r *http.Request) (err error) {
    sid := r.URL.Query().Get("user_id")
    id, err := strconv.Atoi(sid)
    if err == nil {
        return
    }

    u, err := uh.userService.findUser(id)
    if err != nil {
        return
    }
    return uh.view.ExecuteTemplate(w, "user.gohtml", u)
}

func main() {
    fmt.Println("Starting web server...")

    mock := new(mockUserRepo)

    h := &mainHandler{
        view: template.Must(template.ParseGlob("views/*")),
    }
    uh := &userHandler{h, &userService{mock}}

    http.HandleFunc("/", uh.handle(uh.getUser))
    log.Fatal(http.ListenAndServe(":8888", nil))
}


203
5
задан 31 января 2018 в 07:01 Источник Поделиться
Комментарии
1 ответ

Чтобы действительно понять блоге, рекомендую посмотреть на его примере проекта (https://github.com/benbjohnson/wtf - см. также http филиал).

Бен Джонсон написал еще один пост с подробным описанием его действия. https://medium.com/wtf-dial/wtf-dial-domain-model-9655cd523182).

Что касается организации вашего кода, это будет выглядеть так:

project.go // the exported interfaces
mock/ // the mock implementations
http/ // http handler/server
mysql/ // the mysql implementations
cmd/ // the 'glue'

Важным моментом является то, что вы можете импортировать только родитель (суб)пакеты.
Например http подпакет может не зависеть от mysql подпакет (http должно зависеть только от интерфейсов, определенных в project.go - project.UserService например).

Единственным исключением из этого правила является вашим main.go (или тесты).
Например, вы импортируете project/http и project/mysql и соединить их :

Поскольку mysql.UserService struct реализует project.UserService interface он прозрачен для http пакет, который ожидает project.UserService interface


project.go

взгляните на https://github.com/benbjohnson/wtf/blob/http/wtf.go

package project

type UserID int

type User struct {
Name string
}

type UserService interface {
GetByID(UserID) (*User, error)
}

// HTTPService is similar


mock/user.go

взгляните на https://github.com/benbjohnson/wtf/blob/http/mock/mock.go

package mock

import (
"your/project"
)

type UserService struct {
GetByIDFn func(id project.UserID) *project.User, error
GetByIDInvoked bool
}

func (s *UserService) GetByID(id project.UserID) (*project.User, error) {
s.GetByIDInvoked = true
return s.GetByIDFn(id)
}


mysql/user.go

Вашему фактической реализации с базой данных MySQL например (адаптировать в свой внутренний пользователь)


http/*.go

взгляните на https://github.com/benbjohnson/wtf/tree/6d855c355488361b22b1a5ba13d9453e39141292/http

package http

import (
"your/project"
"net/http"
)

type HTTPService struct {
// Here you embed some UserService Interface
UserService project.UserService
}

func (h *HTTPService) HandleHTTP(w http.ResponseWriter, r *http.Request) {
// Use the UserService Interface (don't care if it's mock or real)
h.UserService.GetByID(...)
// write response
}


cmd/mocked/main.go

Клей все (будьте осторожны с именами импорт конфликтов!):

package main

import (
"your/project/http"
nethttp "net/http"
)

func main(){
server := http.HTTPService{
UserService: mock.UserService{},
}

nethttp.ListenAndServe(":8888", server)
}

3
ответ дан 2 февраля 2018 в 08:02 Источник Поделиться