Дизайн лямбда-API написан на Go - PullRequest
0 голосов
/ 02 декабря 2018

Мне нужна помощь с дизайном моего API, написанного на Go.Это структура файла:

database/
 database.go
middlewares/
 authentication.go
models/
 pageview
services/
 pageviews/
   create/
     main.go
   show/
     main.go
   serverless.yml

К настоящему времени у меня есть только служба просмотра страниц.

Позвольте мне показать вам, что находится внутри обработчика, отвечающего за создание просмотра страницы (services / pageviews /)create / main.go):

package main

import (
    "context"

    "github.com/aws/aws-lambda-go/events"
    "github.com/aws/aws-lambda-go/lambda"
    "github.com/clickhound/api/models"
)

func Handler(ctx context.Context, request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
    var pageviews models.Pageview
    if err := pageviews.Create(request.Body); err != nil {
        return events.APIGatewayProxyResponse{}, err
    }

    return events.APIGatewayProxyResponse{
        StatusCode: 201,
    }, nil
}

func main() {
    lambda.Start(Handler)
}

Как видите, обработчик запросов (или контроллер) отвечает за делегирование создания ресурса модели, давайте посмотрим, что внутри модели просмотра страниц:

package models

import (
    "encoding/json"
)

type Pageview struct {
    ID       string
    Hostname string `gorm:"not null"`
}

func (p *Pageview) Create(data string) error {
    if err := json.Unmarshal([]byte(data), p); err != nil {
        return err
    }

    // TODO validate error here.
    db.Create(p)
    return nil
}

Итак, модель отвечает за:

  1. Разобрать тело запроса
  2. Создать новый ресурс

Thisначинает сбиваться, когда мне нужно вернуть данные в контроллер, скажем, у меня есть Find просмотр страницы.Это обработчик запроса (или контроллер):

package main

import (
    "context"

    "github.com/aws/aws-lambda-go/events"
    "github.com/aws/aws-lambda-go/lambda"
    "github.com/clickhound/api/middlewares"
    "github.com/clickhound/api/models"
)

func Handler(ctx context.Context, request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
    var pageview models.Pageview
    data, err := pageview.Find(request.PathParameters["id"])
    if err != nil {
        return events.APIGatewayProxyResponse{}, err
    }

    return events.APIGatewayProxyResponse{
        StatusCode: 200,
        Body:       string(data),
    }, nil
}

func main() {
    lambda.Start(Handler))
}

И модели Find функции:

func (p *Pageview) Find(id string) ([]byte, error) {
    p.ID = id

    // TODO validate error here.
    db.Find(p)
    return json.Marshal(p)
}

В этом случае модель отвечает за:

  1. Найти ресурс
  2. Маршал ресурса в JSON

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

Я введу аутентификацию, и некоторые действия (например, Поиск просмотра страницы) на моделях должны быть ограничены текущим пользователем.Для этого я буду использовать промежуточное программное обеспечение authentication, которое вводит текущего пользователя в пространство имен моделей:

package middlewares

import (
    "context"

    "github.com/aws/aws-lambda-go/events"
    "github.com/clickhound/api/models"
)

func Authentication(next MiddlewareSignature) MiddlewareSignature {
    return func(ctx context.Context, request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
        claims := request.RequestContext.Authorizer["claims"]

        if models.InjectUser(claims).RecordNotFound() {
            return events.APIGatewayProxyResponse{StatusCode: 401}, nil
        }

        return next(ctx, request)
    }
}

И в моделях пользователей:

package models

import (
    "time"

    "github.com/jinzhu/gorm"
    "github.com/mitchellh/mapstructure"
)


type User struct {
    ID        string `gorm:"not null"`
    Email     string `gorm:"not null;unique"`
    CreatedAt time.Time
    UpdatedAt time.Time
}

func InjectUser(claims interface{}) *gorm.DB {
    if err := mapstructure.Decode(claims, user); err != nil {
        panic(err)
    }
    return db.Find(&user)
}

var user User

Теперь,любой обработчик запросов (контроллер), который должен выполнять операции, ограниченные текущим пользователем, я могу изменить:

func main() {
    lambda.Start(middlewares.Authentication(Handler))
}

на:

func main() {
    lambda.Start(
        middlewares.Authentication(Handler),
    )
}

Некоторые вопросы:

  1. Что вы думаете о внедрении пользователя в пространство имен моделей?
  2. Что вы думаете об использовании обработчиков запросов (контроллеров) для вызова только нужной функции?
  3. Что вы думаете о моделях, отвечающих за логику персистентности, проверку действия базы данных, маршалинг / демаршаллинг данных запроса / ответа.

1 Ответ

0 голосов
/ 02 декабря 2018

Полезно использовать какой-то модуль для изоляции бизнес-логики от деталей транспорта.Это просто два разных уровня абстракции и код станет чище, если мы их не смешаем.Тем не менее, это должно быть прагматично, и мы можем сохранить HTTP-коды, поскольку они являются универсальным языком, и в этом нет ничего плохого, если ваша бизнес-логика возвращает 500 и 400 для различных типов ошибок.

Это разделение было бы главной цельюконтроллер, если я напишу этот код.Слой (модель) бизнес-логики должен работать с объектами строгого типа, которые моделируют бизнес-домен, и ему не нужно знать о деталях реализации лямбды HTTP или AWS.

Контроллер для обработки:

  • Маршрутизация
  • Api Versioning
  • Сериализация / десериализация, включая параметры URL, заголовки, специфичные для AWS лямбда-поля и т. Д.

Модель:

  • Получениеи Возврат объектов и ошибок строгого типа
  • Проверка ввода (это может быть частично перемещено в контроллер в зависимости от платформы)
  • IO, включая загрузку User по ID для аутентификации и бизнес-транзакций
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...