LINUX.ORG.RU

Toolkit для web разработки на Go

 , , ,


0

6

Представляю вашему вниманию 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
ChildController.Finally при этом выполнено не будет, т.к. ParentController.Finally вернул finish = true.


generate views - генерация списка файлов

Выше во фрагментах кода использовались следующие конструкции:

return c.RenderTemplate("path/to/dir/file.html")
return c.RenderTemplate(v.Paths.Path.To.Dir.FileHTML)
Второе - экспортируется из автосгенерированного с помощью generate views пакета. Преимущество - отсутствие файла будет обнаружено во время компиляции или запуска конечного приложения, а не во время захода пользователя на сайт.


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
Не исключены баги, ошибки и прочее.


Слишком много магии, слишком громоздко.

Плюс.

// Index is a sample action.
func (c *Profiles) Index() http.Handler {
	return c.RenderTemplate(v.Paths.Profiles.IndexHTML)
}
И вот в примере, откуда мы вдруг получили v? Глобалы как рекомендованная архитектура?

Дальше.

Каждый компонент - независим, может использоваться отдельно от toolkit'а, быть заменён альтернативной реализацией, использоваться с другим фреймворком;

При этом зашел в первый же случайный файл (controllers/templates/handler.go). И что мы там видим? Импорт github.com/anonx/sunplate/log. Это такая особая независимость, когда на самом деле зависим, да еще и без выбора? Окей, подумал я, может там log это такой врапер ридера, скажем. Идём в лог (log/log.go), и видим жесткий логер, который ко всему еще и не очень интересуется куда писать, а просто валит всё в std{err,out}.

В общем я наверно понимаю, только PoC, всётакое. Но зачем тогда это вываливать с заявлениями, которые еще не реализованы? А то что реализовано, местами, сделано плохо или бестолково. Я просто не понимаю как можно реализовать что-то крутое, если оно на этапе базовой архитектуры уже терпит косяки. Но в целом - всех благ и вдохновения. Лучше больше чем меньше, наверно :)

iu0v1 ()
Ответ на: комментарий от iu0v1

И вот в примере, откуда мы вдруг получили v? Глобалы как рекомендованная архитектура?

Нет, сверху экспортируем пакет:

import (
	...
	v "bitbucket.org/yourusername/sample/assets/views"
)

// Index is a sample action.
func (c *Profiles) Index() http.Handler {
	return c.RenderTemplate(v.Paths.Profiles.IndexHTML)
}

При этом зашел в первый же случайный файл (controllers/templates/handler.go). И что мы там видим? Импорт github.com/anonx/sunplate/log. Это такая особая независимость, когда на самом деле зависим, да еще и без выбора?

Опять мимо. Читаем выше:

Чего пока нет:
Стандартных контроллеров (аналог middleware в других фреймворках). Имеются Templates и Requests. Но добавлены лишь с тестовой целью...

Идём в лог (log/log.go), и видим жесткий логер, который ко всему еще и не очень интересуется куда писать, а просто валит всё в std{err,out}.

Это логгер для внутренних нужд toolkit'а. После реализации базовых контроллеров он переедит в internal/.

А то что реализовано, местами, сделано плохо или бестолково.

По существу есть что сказать? Или просто решил поумничать?

smb ()
Ответ на: комментарий от smb

По поводу «магии» - напротив. Всё явно, каждая утила имеет свою спецификацию: получаем на вход X, выдаём пакет Z. Все пакеты штатно подключаются вместе, используя import. «Громоздкость» - вообще не комментирую.

А вообще, складывается мнение, что это такая национальная забава в русскоязычных сообществах: выссказывать своё веское Фи, не особо вникая в суть вопроса. Просто на основании того, что Д'Артаньян, а все 3.14. Во всяком случае, русскоязычное сообщество Golang в GGroups, немногочисленные блоги, а теперь ещё и LOR производят именно такое впечатление.

smb ()
Ответ на: комментарий от smb

А ты хотел типичный интернациональный hugbox, где ты погружаешься в выдрессированные феминацистками и прочими мразями прелые каловые однородные массы? Сорян, у нас тут технический (если не социальный) дарвинизм.

Deleted ()
Ответ на: комментарий от smb

Обратитесь на англоязычные сайты. Вас же никто не принуждает писать именно в русскоязычные сообщества.

Weres ★★★ ()
Ответ на: комментарий от iu0v1

Окей, подумал я, может там log это такой врапер ридера, скажем. Идём в лог (log/log.go), и видим жесткий логер, который ко всему еще и не очень интересуется куда писать, а просто валит всё в std{err,out}.

Жёсткий это как, в смысле эрогированный? По поводу куда писать, он же экспортирован, используй свой логгер.

https://golang.org/pkg/net/http/#pkg-variables

var DefaultClient = &Client{}

DefaultClient is the default Client and is used by Get, Head, and Post.

var DefaultServeMux = NewServeMux()

DefaultServeMux is the default ServeMux used by Serve.

Стандартная библиотека, на минуточку.

RussCox ()
Ответ на: комментарий от RussCox

Блин, ну просто унизил. И такие истины задвинул, к тому же. Явно ты понимаешь о чём речь. Обтекаю. И продолжаю следить за темой, где еще ничего не понятно, в том числе и о тебе.

iu0v1 ()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.