Структура проекта на Go. Контроллеры, модели и представление данных

6 min read

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

В прошлой статье мы научились обрабатывать роут “/” 

package main import ( "fmt" "net/http" "log" ) func sayhello(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Привет!") } func main() { http.HandleFunc("/", sayhello) // Устанавливаем роутер err := http.ListenAndServe(":8080", nil) // устанавливаем порт веб-сервера if err != nil { log.Fatal("ListenAndServe: ", err) } }

Если хотите использовать https,  то вместо ListenAndServe используйте ListenAndServeTLS

err := http.ListenAndServeTLS(":8080", "cert.pem", "key.pem", nil)

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

Одной из самых популярных концепций является MVC, которая используется в большинстве фреймворков. Суть её в том, чтобы разделить сущность на отдельные компоненты, которые отвечают за представление и за состояние данных. Рекомендуем прочитать статью

Итак, что мы хотим получить? В рамках этой статьи мы «разнесем» код по отдельным файлам и реализуем простейший MVC. 

Создаем контроллер

Контроллер — это часть проекта, которая отвечает за управление шаблонами и данными. Пользовательские запросы, в зависимости от роута, попадают в контроллер, который берет на себя задачу собрать необходимые данные из моделей, а затем передает их в шаблон. Обработкой данных контроллер не занимается, а лишь связывает несколько моделей.

Создадим папку с названием «controllers» рядом с main.go, а внутри этой папки создадим файл index.go. Структура будет выглядеть так:

src |-- controllers | +-- index.go +-- main.go

Можете называть файл как угодно, мы назвали так, чтобы показать, что это точка входа в контроллер.

package controllers

В этом файле у нас будет реализация методов, которые обрабатывают все пользовательские запросы. Перенесем обработчик sayHello

package controllers // Sayhello начинается с заглавной для того, чтобы метод был доступен вне этого файла func Sayhello(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Привет!") }

Теперь мы можем использовать публичные методы. Подключим пакет в файле main.go, которая является точкой входа. 

import ( "fmt" "net/http" "log" "myFirstGoApp/controllers" )

А в обработчике укажем метод контроллера

http.HandleFunc("/", controllers.Sayhello) // Обработчик из контроллера

Таким образом, можно распутать код, отделив реализацию обработчиков адресов от инициализации приложения. Попробуйте добавить еще один роут, например, “/sayBye” и передайте в ответ клиенту “Good Bye!”.

Представление View

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

В Go существуют пакеты html/template и text/template для оформления ответа. Первый пакет умеет работать с HTML, чистить лишние атрибуты, закрывать теги и защищаться от XSS. Второй пакет работает намного проще:все переданные теги, атрибуты и спецсимволы будут экранированы и выведутся в виде обычного текста.

Подключим один из этих пакетов в файле controllers.go:

import "html/template"

Теперь, расширим функцию SayHello в контроллере.

func Sayhello(w http.ResponseWriter, r *http.Request) { tmpl, err := template.New("template.html").ParseFiles("template.html") text := "Привет!" tplErr := tmpl.Execute(w, map[string]interface{}{ "text" : text, }) }

И создадим файл template.html

{{ .text }}

Итак, мы подключили пакет для работы с HTML. В функцию template.New передается уникальное имя шаблона, а затем вызывается функция ParseFiles, которая получает данные из файла и хранит в памяти. Далее мы передаем в ответ «w» этот шаблон c данными. В результате исполнения этого кода вы увидите то же самое, только теперь у вас HTML-файл независим. Попробуйте поэкспериментировать и создать несколько обработчиков и шаблонов перед тем, как пойти дальше.

Модели

Модель предоставляет данные и методы для работы с ними, управляет состоянием объекта-сущности и при необходимости может обновить или удалить.

Создадим папку models и файл todo.go внутри этой папки

package models

Добавим несколько методов в togo.go

package models type Todo struct { Name string Done bool } func Get() []Todo { todos := []Todo{ {"Выучить Go", false}, {"Создать сайт", false}, {"Profit", false}, } return todos }

Подключим этот пакет в контроллере и передадим полученные от модели данные в шаблон

import "html/template" func Sayhello(w http.ResponseWriter, r *http.Request) { tmpl, err := template.New("template.html").ParseFiles("template.html") title := "Я пишу сайт на Go!" todos := models.Get() tplErr := tmpl.Execute(w, map[string]interface{}{ "text" : text, "todos" : todos, }) }

Добавим следующий код в template.html

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"/> <title>{{ .title }}</title> </head> <body> {{ range $index, $value := .todos }} <div class="item{{ if eq .Done true }} done {{end}}"> <div class="title">{{ .Name }}</div> </div> {{ end }} </body> </html>

Готово. Все довольно просто.

Для закрепления можете попробовать создать несколько страниц с разными ответами.В следующей статье речь пойдет о базе данных. Мы создадим форму добавления данных, обработку GET и POST запросов, а также удаление и обновление.