Представляю вашему вниманию toolkit для web разработки на Go. Всё ещё на стадии Proof of Concept (не готов к реальному использованию). Основан на идеях, почерпнутых в баг трекере Revel Framework. Однако, отличается от последнего следующим:
- Не используется runtime reflection;
- Отсутствие монолитности;
- Каждый компонент - независим, может использоваться отдельно от toolkit'а, быть заменён альтернативной реализацией, использоваться с другим фреймворком;
- Совместимость со стандартной библиотекой и инструментами;
- Opinionated структура проекта по-умолчанию;
- 100% кастомизация при необходимости: свой роутер, шаблонизатор, layout проекта и всё, всё, всё.
Главная идея - реализовать toolkit в виде набора независимых утил, каждая из которых делает исключительно свою работу. Из доступного на данный момент:
- new - опциональная утила, отвечающая за создание нового проекта (та самая opinionated структура по-умолчанию).
- run - task runner: читает конфигурационный файл в директории проекта, следит за указанными там файлами с целью осуществления в случае их изменения «горячего» (пере)запуска приложения, (пере)компиляции client-side assets (CoffeeScript, SCSS, минификация и конкатенация JS и т.д.) и прочего.
- generate - утилы, отвечающие за генерацию кода. Могут быть использованы (и по-умолчанию используются) совместно с go generate.
- handlers - сгенерировать на основе контроллеров пакет со стандартными HTTP Handler функциями, чтобы их можно было использовать со стандартным роутером или любым другим совместимым с ним.
- views - генерация списка шаблонов во имя type safety.
generate handlers - генерация Handler функций на основе контроллеров.
1. Контроллер - любая стуктура, имеющая action'ы или «магические методы».
2. Action - метод, возвращающий http.Handler в качестве первого параметра.
// Profiles is a sample controller.
type Profiles struct {
}
// Index is a sample action.
func (c *Profiles) Index() http.Handler {
return c.RenderTemplate(v.Paths.Profiles.IndexHTML)
}
Обратите внимание, action'ы могут иметь любое количество аргументов и возвращать любое количество параметров (до тех пор пока первый - http.Handler):
// List is a sample action that gets 2 arguments and returns 3 results.
func (c *Profiles) List(page int, desc bool) (http.Handler, bool, error) {
}
3. «Магические» action'ы Before и After. Обычные action'ы с той лишь разницей что запускаются автоматически перед и после каждого action'а соответственно.
func (c *App) Before() http.Handler {
if c.NotAuthorized() {
return c.RenderTemplate("app/login.html")
}
return nil
}
func (c *App) Index() http.Handler {
...
}
func (c *App) After() http.Handler {
...
}
4. «Магические» методы Initially и Finally подобны «магическим» action'ам в том, что так же запускаются автоматически с каждым запросом. Подробнее описаны здесь.
5. Наследование. Можно наследовать «магические» action'ы и «магические» методы, используя struct embedding.
type ParentController struct {
}
func (c *ParentController) Before() http.Handler {
log.Println("ParentController.Before")
return nil
}
func (c *ParentController) Finally(http.ResponseWriter, *http.Request) (finish bool) {
log.Println("ParentController.Finally")
return true // NB!
}
type ChildController struct {
*ParentController
}
func (c *ChildController) Before() http.Handler {
log.Println("ChildController.Before")
return nil
}
func (c *ChildController) Index() http.Handler {
log.Println("ChildController.Index")
return c.RenderTemplate(v.Paths.ChildController.IndexHTML)
}
func (c *ChildController) Finally() bool {
log.Println("ChildController.Finally")
return false
}
Код выше при запросе к action'у Index напечатает в log следующее:
ParentController.Before
ChildController.Before
ChildController.Index
ParentController.Finally
generate views - генерация списка файлов
Выше во фрагментах кода использовались следующие конструкции:
return c.RenderTemplate("path/to/dir/file.html")
return c.RenderTemplate(v.Paths.Path.To.Dir.FileHTML)
run - file watcher / task runner
Образец конфигурационного файла:
init:
- /pass # Ничего не делать
- /start build # Запустить команды асинхронно
- /single app # Запустить единственный инстанс команды
- npm install coffeescript # Выполнить произвольную команду
watch:
./controllers:
- /run build # Запустить и дождаться результата
- /single app # Перезапустить ранее запущенный инстанс
build:
- go build -o ./bin/app github.com/username/project
app: ./bin/app --port $PORT --host $HOST
Чего пока нет:
- Стандартных контроллеров (аналог middleware в других фреймворках). Имеются Templates и Requests. Но добавлены лишь с тестовой целью, первый делает доступным метод RenderTemplate и поле Context, второй - вызывает ParseForm, чтобы была возможность использовать параметры, переданные, например, методом POST. Сессий, кеша и т.п. нет.
- Reverse Proxy, чтобы показывать ошибки в окне браузера, а не только в консоли.
- Есть double array based роутер, основанный на denco router. Но нет генерации роутов и обратных роутов.
- Сейчас работаю над удобным механизмом генерации форм и валидации полученных от пользователя данных (т.н. autoform): чтобы в одном месте описать форму и использовать это описание и на клиенте и на сервере и при авто валидации.
Как опробовать то что уже есть?
# Установливаем toolkit, убеждаясь, что используем последнюю версию.
go get -u github.com/anonx/sunplate
# Создаём новый проект.
sunplate new bitbucket.com/yourusername/sample
# Запускаем проект.
sunplate run bitbucket.com/yourusername/sample
# Заходим на http://localhost:8080 чтобы увидеть приложение.
# Файлы проекта можно найти в директории
# $GOPATH/src/bitbucket.com/yourusername/sample