Обработчики Golang обрабатывающие различные типы


Это Аппхандлеры из шаблона, который я нашел в интернете, исследуя gorilla/mux. Они являются частью структуры, которая удовлетворяет http.Обработчик. Если вы заметили, следующие два блока точно такие же. Фактически, они могут быть переданы "варианту" ("потоку" или "процессу") в виде строки.

func CreateFlow(a *AppContext, w http.ResponseWriter, r *http.Request) (int, error) {

    highest, code, err := a.Create("flow", r)
    if code != 200 || err != nil {
        return code, err
    }

    b := new(bytes.Buffer)
    json.NewEncoder(b).Encode(struct {
        Highest int `json:"id"`
    }{highest})
    w.Header().Set("Content-Type", "application/json")
    w.Write(b.Bytes())
    return 200, nil
}

func CreateProcess(a *AppContext, w http.ResponseWriter, r *http.Request) (int, error) {

    highest, code, err := a.Create("process", r)
    if code != 200 || err != nil {
        return code, err
    }

    b := new(bytes.Buffer)
    json.NewEncoder(b).Encode(struct {
        Highest int `json:"id"`
    }{highest})
    w.Header().Set("Content-Type", "application/json")
    w.Write(b.Bytes())
    return 200, nil
}
Однако следующие два блока нуждаются не только в строке, но и в переменной соответствующего типа ("поток" и "процесс"), чтобы успешно отменить попадание, которое я получаю от ElasticSearch. Кроме того, они представляют собой идентичный код.
func GetFlow(a *AppContext, w http.ResponseWriter, r *http.Request) (int, error) {

    hit, code, err := a.GetByID("flow", mux.Vars(r)["id"], r)
    if code != 200 {
        return code, err
    }

    var flow Flow

    err = json.Unmarshal(*hit.Source, &flow)
    if err != nil {
        return 500, err
    }

    flow.ESID = hit.Id

    b := new(bytes.Buffer)
    json.NewEncoder(b).Encode(flow)
    w.Header().Set("Content-Type", "application/json")
    w.Write(b.Bytes())
    return 200, nil
}

func GetProcess(a *AppContext, w http.ResponseWriter, r *http.Request) (int, error) {

    hit, code, err := a.GetByID("process", mux.Vars(r)["id"], r)
    if code != 200 {
        return code, err
    }

    var process Process

    err = json.Unmarshal(*hit.Source, &process)
    if err != nil {
        return 500, err
    }

    process.ESID = hit.Id

    b := new(bytes.Buffer)
    json.NewEncoder(b).Encode(process)
    w.Header().Set("Content-Type", "application/json")
    w.Write(b.Bytes())
    return 200, nil
}

Я не знаю, как обобщить это поведение в golang, когда речь идет об объявленном типе. Эти обработчики тоже находятся в одном пакете, так как я думаю, что все они выполняют одну и ту же задачу. Я очень четко повторяю себя в коде, но мне нужен совет о том, как я могу улучшить. Я прошел мимо "немного копирования лучше, чем немного зависимости."но я боюсь, потому что "отражение никогда не бывает ясным".

Вот пример: пример объявления в main с использованием одной из этих функций.

api.Handle("/flow/{id:[0-9]+}", handlers.AppHandler{context, handlers.GetFlow}).Methods("GET")
2 2
dry

2 ответа:

Хорошо, я предлагаю решение, которое даст вам максимальное повторное использование кода и минимальное копирование кода. Это, на мой взгляд, самое общее решение. Мы также примем во внимание ответ, данный https://stackoverflow.com/users/7426/adrian для завершения решения. Вам нужно только определить одну функцию, которая будет функцией более высокого порядка CreateHandler, которая будет возвращать функцию следующей сигнатуры:
func(*AppContext, http.ResponseWriter, http.Request) (int, error).

Эта подпись является фактической подпись обработчика, который должен использоваться в качестве конечной точки mux. Решение включает в себя определение типа Handler, который является структурой, имеющей три поля:

handlerType: думайте об этом как о перечислении, имеющем либо значение "CREATE", либо "GET". Это позволит решить, какой из двух блоков кода, которые вы вставили в свой вопрос, мы должны использовать.

handlerActionName: это подскажет "CREATE" или "GET", какой эластичный использовать. Значение должно быть либо "flow", либо "process".

elastible: это будет ... Тип интерфейса Elastible, который будет иметь функцию SetESID. Мы будем использовать это, чтобы отправить наши типы Flow или Process в наш Handler. Таким образом, и Flow, и Process должны удовлетворять нашему интерфейсу. Это сделает решение еще более универсальным и вызовет только handler.elastible.SetESID() , и мы вставим ESID независимо от того, что базовый тип в 'elastible' может быть либо 'Flow', либо 'Process'

Я также определяю функцию sendResponse(response interface{}), которую мы будем повторно использовать для отправки ответа. Он приобретает w http.ResponseWriter используя закрытие. Таким образом, response может быть чем угодно, а

struct {
    Highest int `json:"id"`
}{highest} 

Или Flow или Process. Это также сделает эту функцию универсальной.

Полное решение было бы теперь.

// This is the type that will be used to build our handlers.
type Handler struct {
    handlerType       string    // Can be "CREATE" or "GET"
    handlerActionName string    // Can be "flow" or "process"
    elastible         Elastible // Can be *Flow or *Process
}

// Your ESID Type.
type ESIDType string

// Solution proposed by https://stackoverflow.com/users/7426/adrian.
type Elastible interface {
    SetESID(id ESIDType)
}

// Make the Flow and Process pointers implement the Elastible interface.
func (flow *Flow) SetESID(id ESIDType) {
    flow.ESID = id
}

func (process *Process) SetESID(id ESIDType) {
    process.ESID = id
}

// Create a Higher Order Function which will return the actual handler.
func CreateHandler(handler Handler) func(*AppContext, http.ResponseWriter, http.Request) (int, error) {

    return func(a *AppContext, w http.ResponseWriter, r http.Request) (int, error) {

        // Define a sendResponse function so that we may not need to copy paste it later.
        // It captures w using closure and takes an interface argument that we use to call .Encode() with.

        sendResponse := func(response interface{}) (int, error) {
            b := new(bytes.Buffer)
            json.NewEncoder(b).Encode(response)
            w.Header().Set("Content-Type", "application/json")
            w.Write(b.Bytes())
            return 200, nil
        }

        // Define these variables beforehand since we'll be using them
        // in both the if and else block. Not necessary really.
        var code int
        var err error

        // Check the handlerType. Is it create or get?
        if handler.handlerType == "CREATE" {
            var highest int

            // Creates the thing using handler.handlerActionName which may be "flow" or "process"
            highest, code, err = a.Create(handler.handlerActionName, r)
            if code != 200 || err != nil {
                return code, err
            }

            // Send the response using the above defined function and return.
            return sendResponse(struct {
                Highest int `json:"id"`
            }{highest})

        } else {

            // This is GET handlerType.
            var hit HitType

            // Get the hit using again the handler.handlerActionName which may be "flow" or "process"
            hit, code, err = a.GetByID(handler.handlerActionName, mux.Vars(r)["id"], r)
            if code != 200 || err != nil {
                return code, err
            }

            // Do the un-marshalling.
            err = json.Unmarshal(*hit.Source, ob)
            if err != nil {
                return 500, err
            }

            // We have set the handler.elastible to be an interface type
            // which will have the SetESID function that will set the ESID in the
            // underlying type that will be passed on runtime.
            // So the ESID will be set for both the Flow and the Process types.
            // This interface idea was given inside an earlier answer by
            // https://stackoverflow.com/users/7426/adrian

            handler.elastible.SetESID(hit.id)
            return sendResponse(handler.elastible)
        }
    }
}

И вы бы настроили свои конечные точки mux, используя следующий код.

    // This was your first function. "CreateFlow"
    api.Handle("/createFlow/{id:[0-9]+}", handlers.AppHandler{
        context, CreateHandler(Handler{
            elastible:         &Flow{},
            handlerActionName: "flow",
            handlerType:       "CREATE",
        }),
    }).Methods("GET")

    // This was your second function. "CreateProcess"
    api.Handle("/createProcess/{id:[0-9]+}", handlers.AppHandler{
        context, CreateHandler(Handler{
            elastible:         &Process{},
            handlerActionName: "process",
            handlerType:       "CREATE",
        }),
    }).Methods("GET")

    // This was your third function. "GetFlow"
    api.Handle("/getFlow/{id:[0-9]+}", handlers.AppHandler{
        context, CreateHandler(Handler{
            elastible:         &Flow{},
            handlerActionName: "flow",
            handlerType:       "GET",
        }),
    }).Methods("GET")

    // This was your fourth function. "GetProcess"
    api.Handle("/getProcess/{id:[0-9]+}", handlers.AppHandler{
        context, CreateHandler(Handler{
            elastible:         &Process{},
            handlerActionName: "process",
            handlerType:       "GET",
        }),
    }).Methods("GET")


Надеюсь, это поможет!

Вы можете сделать это, передав пример нужного типа, так же, как это делает Unmarshal:

func GetFlow(a *AppContext, w http.ResponseWriter, r *http.Request) (int, error) {
    return GetThing(a,w,r,"flow",new(Flow))
}

func GetProcess(a *AppContext, w http.ResponseWriter, r *http.Request) (int, error) {
    return GetThing(a,w,r,"process",new(Process))
}

func GetThing(a *AppContext, w http.ResponseWriter, r *http.Request, t string, ob Elastible{}) (int, error) {
    hit, code, err := a.GetByID(t, mux.Vars(r)["id"], r)
    if code != 200 {
        return code, err
    }

    err = json.Unmarshal(*hit.Source, ob)
    if err != nil {
        return 500, err
    }

    ob.SetESID(hit.Id)

    b := new(bytes.Buffer)
    json.NewEncoder(b).Encode(ob)
    w.Header().Set("Content-Type", "application/json")
    w.Write(b.Bytes())
    return 200, nil
}

type Elastible interface {
    SetESID(id ESIDType)    // whatever type ESID is, not clear from example
}

func (f *Flow) SetESID(id ESIDType) {
    f.ESID = id
}

Этот код непроверен (потому что у меня нет вашего struct defs или другого зависимого кода), но я надеюсь, что он донесет идею.