Перейти к веб-архитектуре

Go

исходный адрес

Использование Golang для создания веб-проектов — проблема. Если вы используете Rails, вы, вероятно, не почувствуете этого. я здесьRefer MadnessИспользуется следующая архитектура.

-public/
-views/
-models/
-utils/
-controllers/
-web/
-main.go

我将打破这个体系,介绍各个目录的作用。在每个目录下,文件只能访问其同级或者上一级。 это означаетutilsдоступ только к себе иmodels,webможет получить доступ только к себе,controllers,utils,models.modelsможет получить к нему доступ только сам. Я делаю это, чтобы сохранить ясность иерархии и предотвратить рекурсивные зависимости. Теперь давайте проанализируем роль каждого каталога и файла.

main.go

main.goЭто основной файл для создания зависимостей, получения переменных среды и запуска служб. Ниже приведен пример этого.

package main

import (
  "github.com/larryprice/refermadness/utils"
  "github.com/larryprice/refermadness/web"
  "github.com/stretchr/graceful"
  "os"
)

func main() {
  isDevelopment := os.Getenv("ENVIRONMENT") == "development"
  dbURL := os.Getenv("MONGOLAB_URI")
  if isDevelopment {
    dbURL = os.Getenv("DB_PORT_27017_TCP_ADDR")
  }

  dbAccessor := utils.NewDatabaseAccessor(dbURL, os.Getenv("DATABASE_NAME"), 0)
  cuAccessor := utils.NewCurrentUserAccessor(1)
  s := web.NewServer(*dbAccessor, *cuAccessor, os.Getenv("GOOGLE_OAUTH2_CLIENT_ID"),
    os.Getenv("GOOGLE_OAUTH2_CLIENT_SECRET"), os.Getenv("SESSION_SECRET"),
    isDevelopment, os.Getenv("GOOGLE_ANALYTICS_KEY"))

  port := os.Getenv("PORT")
  if port == "" {
    port = "3000"
  }

  graceful.Run(":"+port, 0, s)
}

так какmain.goдействительно самый низкий уровень, поэтому он имеет доступ ко всем каталогам: в данном случае этоwebиutils. Получите все переменные среды здесь и введите их, где это необходимо. существуетmain.goСоздается сервер, внедряются зависимости, и сервер запускается на настроенном порту.

web

webКаталог является основным сервисным кодом, но также включает промежуточный код. НижеwebВнутренняя структура каталога:

-web/
|-middleware/
|-server.go

server.goОн содержит определение веб-сервера. Веб-сервер создает весь веб-контроллер и добавлен в промежуточное программное обеспечение Negroni. Вот его пример:

package web

import (
  "github.com/codegangsta/negroni"
  "github.com/goincremental/negroni-sessions"
  "github.com/goincremental/negroni-sessions/cookiestore"
  "github.com/gorilla/mux"
  "github.com/larryprice/refermadness/controllers"
  "github.com/larryprice/refermadness/utils"
  "github.com/larryprice/refermadness/web/middleware"
  "github.com/unrolled/secure"
  "gopkg.in/unrolled/render.v1"
  "html/template"
  "net/http"
)

type Server struct {
  *negroni.Negroni
}

func NewServer(dba utils.DatabaseAccessor, cua utils.CurrentUserAccessor, clientID, clientSecret,
  sessionSecret string, isDevelopment bool, gaKey string) *Server {
  s := Server{negroni.Classic()}
  session := utils.NewSessionManager()
  basePage := utils.NewBasePageCreator(cua, gaKey)
  renderer := render.New()

  router := mux.NewRouter()

  // ...

  accountController := controllers.NewAccountController(clientID, clientSecret, isDevelopment, session, dba, cua, basePage, renderer)
  accountController.Register(router)

  // ...

  s.Use(sessions.Sessions("refermadness", cookiestore.New([]byte(sessionSecret))))
  s.Use(middleware.NewAuthenticator(dba, session, cua).Middleware())
  s.UseHandler(router)
  return &s
}

Serverструктура представляет собойnegroni.Negroniвеб-сервер, в этом файле естьutilsи другие сторонние ссылки на пакеты, создайте маршрутизатор, несколько контроллеров и зарегистрируйте контроллер на текущем маршрутизаторе. В то же время также внедряется необходимое промежуточное программное обеспечение. Говоря о промежуточном программном обеспечении, вот его код:

package middleware

import (
  "github.com/codegangsta/negroni"
  "github.com/larryprice/refermadness/utils"
  "net/http"
)

type Database struct {
  da utils.DatabaseAccessor
}

func NewDatabase(da utils.DatabaseAccessor) *Database {
  return &Database{da}
}

func (d *Database) Middleware() negroni.HandlerFunc {
  return func(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
    reqSession := d.da.Clone()
    defer reqSession.Close()
    d.da.Set(r, reqSession)
    next(rw, r)
  }
}

Это промежуточное ПО аннотаций для доступа к сеансам базы данных через HTTP-маршрутизатор. Базовый файл взят изСообщение в блоге Брайана Гесиака о RESTful Go, изменил его в соответствии с моим файлом.

controllers/

Контроллер здесь очень похож на концепцию контроллера в Rails. Контроллер регистрирует собственный маршрутизатор и устанавливает собственные вызовы функций. Краткий пример выглядит следующим образом:

package controllers

import (
  "encoding/json"
  "errors"
  "github.com/gorilla/mux"
  "github.com/larryprice/refermadness/models"
  "github.com/larryprice/refermadness/utils"
  "gopkg.in/mgo.v2/bson"
  "gopkg.in/unrolled/render.v1"
  "html/template"
  "net/http"
  "strings"
)

type ServiceControllerImpl struct {
  currentUser utils.CurrentUserAccessor
  basePage    utils.BasePageCreator
  renderer    *render.Render
  database    utils.DatabaseAccessor
}

func NewServiceController(currentUser utils.CurrentUserAccessor, basePage utils.BasePageCreator,
  renderer *render.Render, database utils.DatabaseAccessor) *ServiceControllerImpl {
  return &ServiceControllerImpl{
    currentUser: currentUser,
    basePage:    basePage,
    renderer:    renderer,
    database:    database,
  }
}

func (sc *ServiceControllerImpl) Register(router *mux.Router) {
  router.HandleFunc("/service/{id}", sc.single)
  // ...
}

// ...

type serviceResult struct {
  *models.Service
  RandomCode *models.ReferralCode
  UserCode   *models.ReferralCode
}

type servicePage struct {
  utils.BasePage
  ResultString string
}

func (sc *ServiceControllerImpl) single(w http.ResponseWriter, r *http.Request) {
  data, err := sc.get(w, r)

  if len(r.Header["Content-Type"]) == 1 && strings.Contains(r.Header["Content-Type"][0], "application/json") {
    if err != nil {
      sc.renderer.JSON(w, http.StatusBadRequest, map[string]string{
        "error": err.Error(),
      })
      return
    }
    sc.renderer.JSON(w, http.StatusOK, data)
    return
  } else if err != nil {
    http.Error(w, err.Error(), http.StatusBadRequest)
  }

  resultString, _ := json.Marshal(data)
  t, _ := template.ParseFiles("views/layout.html", "views/service.html")
  t.Execute(w, servicePage{sc.basePage.Get(r), string(resultString)})
}

utils/

существуетutilsФайлы в каталоге предоставляют некоторые низкоуровневые функции для поддержки другого кода. В этом примере главное — определить структуру для доступа к контексту запроса, обработки сеанса и определения специальной страницы для наследования. Вот пример настройки пользователя через контекст доступа:

package utils

import (
  "github.com/gorilla/context"
  "github.com/larryprice/refermadness/models"
  "net/http"
)

type CurrentUserAccessor struct {
  key int
}

func NewCurrentUserAccessor(key int) *CurrentUserAccessor {
  return &CurrentUserAccessor{key}
}

func (cua *CurrentUserAccessor) Set(r *http.Request, user *models.User) {
  context.Set(r, cua.key, user)
}

func (cua *CurrentUserAccessor) Clear(r *http.Request) {
  context.Delete(r, cua.key)
}

func (cua *CurrentUserAccessor) Get(r *http.Request) *models.User {
  if rv := context.Get(r, cua.key); rv != nil {
    return rv.(*models.User)
  }
  return nil
}

models/

модель — это структура данных, используемая для описания данных в базе данных. В этом примере модель используется для доступа к базе данных, создается пустая модель, а затем присваивается значение путем запроса к базе данных. Ниже приведен пример:

package models

import (
  "gopkg.in/mgo.v2"
  "gopkg.in/mgo.v2/bson"
  "strings"
  "time"
)

type Service struct {
  // identification information
  ID          bson.ObjectId `bson:"_id"`
  Name        string        `bson:"name"`
  Description string        `bson:"description"`
  URL         string        `bson:"url"`
  Search      string        `bson:"search"`
}

func NewService(name, description, url string, creatorID bson.ObjectId) *Service {
  url = strings.TrimPrefix(strings.TrimPrefix(url, "http://"), "https://")
  return &Service{
    ID:            bson.NewObjectId(),
    Name:          name,
    URL:           url,
    Description:   description,
    Search:        strings.ToLower(name) + ";" + strings.ToLower(description) + ";" + strings.ToLower(url),
  }
}

func (s *Service) Save(db *mgo.Database) error {
  _, err := s.coll(db).UpsertId(s.ID, s)
  return err
}

func (s *Service) FindByID(id bson.ObjectId, db *mgo.Database) error {
  return s.coll(db).FindId(id).One(s)
}

func (*Service) coll(db *mgo.Database) *mgo.Collection {
  return db.C("service")
}

type Services []Service

func (s *Services) FindByIDs(ids []bson.ObjectId, db *mgo.Database) error {
  return s.coll(db).Find(bson.M{"_id": bson.M{"$in": ids}}).Sort("name").All(s)
}

func (*Services) coll(db *mgo.Database) *mgo.Collection {
  return db.C("service")
}

views/

Поместите файлы шаблонов Golang вviewsПод содержанием. Таким образом, независимо от того, какой механизм шаблонов можно разместить непосредственно вviewsВниз.

public/

Как и прежде, этот файл является общедоступным, напримерcss,img,scripts.

как бежать

Без сомнения, мой фаворитdocker, поэтому я буду использовать его для запуска приложения. Если вы не используете докер, вы можете поместить файл в$GOPATH/src/github.com/larryprice/refermadness,бегатьgo getЧтобы получить все зависимости, затем запуститьgo run main.goилиgo build; ./refermadnessЗапустите программу. Если вам также нравится использовать докер, вы можете напрямую передатьDockerfileбежать.

FROM golang:1.4

RUN go get github.com/codegangsta/gin

ADD . /go/src/github.com/larryprice/refermadness
WORKDIR /go/src/github.com/larryprice/refermadness
RUN go get

Мне также нравитсяcompose, поэтому я также запускаю все приложения через файл компоновки. Я использовал АО, SASS и mongodb, так что вот мойdocker-compose.ymlдокумент.

main:
  build: .
  command: gin run
  env_file: .env
  volumes:
    - ./:/go/src/github.com/larryprice/refermadness
  working_dir: /go/src/github.com/larryprice/refermadness
  ports:
    - "3000:3000"
  links:
    - db
sass:
  image: larryprice/sass
  volumes:
    - ./public/css:/src
jsx:
  image: larryprice/jsx
  volumes:
    - ./public/scripts:/src
db:
  image: mongo:3.0
  command: mongod --smallfiles --quiet --logpath=/dev/null
  volumes_from:
    - dbvolume
dbvolume:
  image: busybox:ubuntu-14.04
  volumes:
    - /data/db

Затем запуститеdocker-compose upЗапустить все контейнеры и запустить сервер.