Skip to content

Instantly share code, notes, and snippets.

@nikkomiu
Last active December 15, 2018 14:37
Show Gist options
  • Save nikkomiu/3e92cfe44db7e864726b6347acde7ba4 to your computer and use it in GitHub Desktop.
Save nikkomiu/3e92cfe44db7e864726b6347acde7ba4 to your computer and use it in GitHub Desktop.
Golang App Structure & Deploy

Golang Application Structure and Development

App Directory Structure

  • bin/ (Generated) The build output of the application
  • cmd/ The commands avaliable for running the application
  • handler/ The handlers for the requests
  • route/ The route configuration for handlers
  • db/
    • repo/ The Data Access Layer (DAL) for the database
      • sql/ SQL files for Repo
      • sql.go The generated Go file for the files in the SQL sub-directory
    • migration/
      • sql/
      • sql.go The generated Go file for the files in the SQL sub-directory
    • seed/
      • sql/
      • sql.go The generated Go file for the files in the SQL sub-directory
  • service/ Services for interacting with multiple sub-layers at once
  • worker/ Background job executors
  • mail/ Email templates
  • web/ Web UI
  • pkg/ Common utility packages for various parts of the application
  • public/ (Generated) The output of building the React app
  • vendor/ (Generated) The vendor packages for the application
  • .editorconfig The editor configuration file defining how files should be laid out
  • .gitlab-ci.yml The Gitlab CI configuration
  • LICENSE The license for the project
  • README.md The main README document
  • Dockerfile The Docker image build file
  • Makefile The set of commands avaliable for running, building, and setting up working project

Dependency Injection

Repo

type ProjectRepo interface {
  GetNames(uint64, uint64) []string
}

type Project struct {
  DB *sqlx.DB
}

func (p Project) GetNames(id, userID uint64) []string {
  return p.DB.Get("SELECT name FROM projects WHERE id = $1 AND user_id = $2", id, userID)
}

Service

type ProjectService interface {
  GetNames(uint64, uint64) []string
}

type Project struct {
  ProjectRepo repo.ProjectRepo
}

func (p Project) GetNames(id, userID uint64) []string {
  return p.GetNames(id, userID)
}

Handler

type Project struct {
  ProjectService service.Project
}

func (p Project) GetNames() http.Handler {
  return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
    var (
      id = ps.Get("id")
      userID = r.URL.Query().Get("userId")
    )

    if id == 0 || userID == 0 {
      http.Error(w, "ID and User ID are both required")
      return
    }

    names := p.ProjectService.GetNames(id, userID)

    json.NewEncoder(w).Encode(names)
  }
}

func (p Project) GetManyNames() http.Handler {
  type request struct {
    IDs []uint64 `json:"ids"`
    UserID uint64 `json:"userId"`
  }

  return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
    var req request
    json.NewDecoder(r.Body).Decode(req)

    respChan = make(chan []string)

    for _, id := range req.IDs {
      go func() { respChan <- p.ProjectService.GetNames(id, req.UserID) }()
    }

    var res []string

    for i := 0; i < len(req.IDs); i++ {
      select {
      case names <- respChan:
        res = append(res, names)
      }
    }

    json.NewEncoder(w).Encode(res)
  }
}

Route

func Project(r httprouter.Router, h handler.Project) {
  r.GET("/projects/:id/names", h.GetNames())
}

Main

const (
  databaseURL = "postgres://postgres:postgres@localhost:5433/test_db"
  addr = ":3000"
  publicDir = "public"
)

func serveStatic(r httprouter.Router, dir string) {
  // if public directory exists
  //   serve static files from public
  // else
  //   log warning that static files not set up
}

func main() {
  // Connect to Resources
  db, err := sqlx.Dial(databaseURL)
  if err != nil {
    log.Fatal(err)
  }

  // Create Repos
  projectRepo := &repo.Project{DB: db}

  // Create Services
  projectService := &service.Project{ProjectRepo: projectRepo}

  // Create Handlers
  projectHandler := &handler.Project{ProjectService: projectService}

  // Create the router
  router := httprouter.New()

  // Add Routes
  route.Project(router, projectHandler)

  // Serve Static
  serveStatic(router, publicDir)

  // Start the Server
  log.Fatal(http.ListenAndServe(addr, router))
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment