I am switching to Go from Python/Django. In Django I really liked about its modular Apps project structure design, where each App would have separate bussiness models, routes and views. All the apps would then communicate within the central/project's main routing system and so on.
Django project structure Eg:
- myproject
- myproject
- urls.py
- views.py
...
- planner
- urls.py
- views.py
- models.py
...
I am trying to achieve similar project design in Go project:
- myproject
- cmd
- api
- main.go
- routes.go
- handlers.go
- planner
- routes.go
- handlers.go
- models.go
Excerpt from cmd/api/main.go:
package main
...
db, err := sql.Open("pgx", cfg.db.dsn)
...
srv := &http.Server{
Addr: fmt.Sprintf("localhost:%d", app.config.port),
Handler: app.routes()
}
...
Excerpt from cmd/api/routes.go:
package main
func (app *application) routes() *httprouter.Router {
router := httprouter.New()
planner.Routes(router)
return router
}
Excerpt from cmd/planner/routes.go:
package planner
...
func Routes(router *httprouter.Router) {
router.HandlerFunc(http.MethodPost, "/v1/planner/todos", CreateTodoHandler)
}
Excerpt from cmd/planner/models.go:
package planner
type PlannerModel struct {
DB *sql.DB
}
func (p PlannerModel) InsertTodo(todo *Todo) error {
query := `INSERT INTO todos (title, user_id)
VALUES ($1, $2)
RETURNING id, created_at`
return p.DB.QueryRow(query, todo.Title, todo.UserID).Scan(&todo.ID, &todo.CreatedAt)
}
Now the problem is I need to use the DB connection initialized in cmd/api/main.go file from package main into cmd/planner/handlers.go. Since the variable is from main package I cannot import it into my app's (planner) handler functions.
package planner
func CreateTodoHandler(w http.ResponseWriter, r *http.Request) {
var input struct {
Title string `json:"title"`
UserID int64 `json:"user_id"`
}
err := helpers.ReadJSON(w, r, &input)
...
todo := &Todo{
Title: input.Title,
UserID: input.UserID,
}
...
// How to use/inject DB connection dependency into the PlannerModel?
pm := PlannerModel{
DB: // ????
}
err = pm.InsertTodo(todo)
...
}
I think having a global DB variable solves the problem or a reasonable answer I found was to declare the variable outside in a separate package, and initialize it in main.go. Another approach would be to make the Planner/handlers.go to be of package main and create an application struct in the main package to hold all the models of the project and use it inside the handlers, but I guess this would defeat the decoupled architecture design.
I was wondering what is the preferred way or if there are better ways to go about having similar decoupled project structure like Django, and still use variables initialized in main.go into other packages and do tests?