[Из песочницы] Пишем backend на go с минимальными усилиями при помощи GoJs
Я видел кучу статей на эту тему (я думаю вы тоже видели и сейчас думаете, что это очередной шлак) и всё сводилось к описанию роута на каждый api вызов, и в итоге мы получали кучку кода с которым перейти на тот же jsonp требовало пару дней (или недель). Также я часто встречал на тостере ответы, в которых писали, что нода сразу работает на всём (ajax, ws, jsonp, rpc-json). Правда ли это, я не знаю, но всё же мне пришло в голову исправить это. Я для своего проекта сделал апи сразу по трём протоколам, а именно: ajax, ws, jsonp (по сети ходит что то похожое на json-rpc).
Вот что мы имеем на сервере:
package main
import (
api "github.com/v-grabko/GoJsBackend"
"log"
)
func Testing(ps map[string]string) map[string]string {
ps["test"] = "Testing"
return ps
}
func main() {
api.AddMethod("TestMethod", func(ps map[string]string) map[string]string {
ps["test"] = "TestMethod"
return ps
})
api.AddMethod("Testing", Testing)
log.Fatal(api.RunServer(":8080"))
}
Теперь опишу что здесь происходит. Метод api.AddMethod добавляет новый метод в пулл (метод принимает map и обязан вернуть map). Здесь тест. функции просто к полученным данным добавляют ещё данные и возвращают их. Потом api.RunServer запускает апи сервер на порту 8080.
Данные по сети ходят в формате json. Формат ajax и ws одинаков.
type MyJsonNameB struct {
Data map[string]string `json:"data"`
Method string `json:"method"`
}
А для jsonp он немного другой:
type MyJsonName struct {
C string `json:"c"`
Data map[string]string `json:"data"`
Method string `json:"method"`
}
К всему этому добру я обращался из js. Со временем я сделал авто выбор протокола и чуть поже я сделал абстракцию над 3 протоколами, а в абстракцию пихал драйвер протокола, который реализовал метод driver.send (data, call); . Я тогда разогнался с написанием плюшек к этому всему и слепил что-то вроде микро фреймворка (подобие MVC, только вместо моделей мы делаем запросы к API прямо в контроллере).
Потом я подумал, что неплохо бы поделиться своей работой с сообществом. Так я создал GoJs. Он довольно мал, но имеет почти всё, что нужно (а то, чего нет сделаю; предлагайте в коментарии, на почту v.grabko@box.ua, а в идеале в Pull Requests).
Итак, начнём погружение в этот мини фреймворк. Сначала необходимо создать скелет.
Теперь в корне проекта создаём три директории после создания клонируем в /js/GoJs фреймворк. Теперь запустим тестовый GoJsBackend сервер:
/app/controllers
/app/views
/js/GoJs
Во время запуска мы написали, что хотим вызвать из контроллера start метод index. Пришло время его создать. В директории /app/controllers создадим start.js.
Скелет контроллера (у всех он одинаков):
start = {
index: function () {}
};
Что же сдесь происходит? Мы создаём обьект контроллера (фреймворк помистит его в обьект RegistryLoad.controllers).
Важно! После первой загрузки контроллер кешируется и потом повторно используется. Чтобы его заново загрузить, необходимо перезагрузить страницу.
Теперь, когда скелет создан, давайте сделаем запрос к серверу и полученые данные запихнём во вьюшку:
start = {
index: function () {
window.backend.Get({
method: "TestMethod",
data: {
event: "PageSkeleton",
data: "0"
}
}, function (t) {
View("index", t);
});
}
};
Теперь создаём в директории /app/views вьюшку. Все вьюшки имеют расширение .tpl по этому имя файла будет index.tpl со следующим содержанием:
{{var.event}}
{{var.data}}
{{var.test}}
А что, если нам необходимо выполнить несколько запросов к api? Вы наверное подумали, что необходимо сделать как-то так:
var modelData = {};
window.backend.Get({
method: "TestMethod",
data: {
event: "PageSkeleton",
data: "0"
}
}, function (t) {
modelData.TestMethod = t.test
window.backend.Get({
method: "Testing",
data: {
event: "PageSkeleton",
data: "0"
}
}, function (t) {
modelData.Testing = t.test
View("index", modelData);
});
});
{{var.TestMethod}}
{{var.Testing}}
Да, это работает. Но это, если честно, адский говнокод. Для этого создана абстракция, которая выполняет асинхронные запросы «синхронно». Вы наверное спросите: зачем городить велосипед, если можно использовать синхронные запросы? Всё очень просто. Они подвесят браузер.
window.backend.GetSync([
{
method: "TestMethod",
data: {
event: "PageSkeleton",
data: "0"
}
},{
method: "Testing",
data: {
event: "PageSkeleton",
data: "0"
}
}
],function(ret){
View("index", {
TestMethod : ret.TestMethod.test,
Testing : ret.Testing.test
});
});
А что, если мы захотим данные из одного контроллера передать в другой? Об этом будет написано чуть ниже.
Роутинг
Роутер работает одновременно из html5.history и location.hash (вы абстрагированы). Все маршруты прописаны прямо во вьюшках. Их нет как таковых. Они автоматически парсятся после генерации ссылки. Например, мы захотим вызвать из контроллера errors метод NotFound
{{href.RedHref|/errors/NotFound|Вызвать метод}}
Итак, что тут происходит:
RedHref →всё что содержится здесь будет помещенно в атрибут class=» ссылки
/errors/NotFound →здесь мы первым параметром передали имя контроллера, а вторим его метод.
Вызвать метод → имя ссылки
А что, если мы захотели переменную передать в другой контролеер? Запросто:
{{href.RedHref|/errors/NotFound/vars/{{var.test}}|Вызвать метод}}
А в вызванном контроллере пишем:
errors = {
NotFound:function(){
alert(router.GetData("vars"));
},
};
Но всё не так сладко как кажется. К примеру, сейчас почему-то в Опере мини абстракция window.backend.GetSync не работает вообще. Там почему-то не работает рекурсия и из-за этого колбек не отработает.
github.com/v-grabko/GoJs
github.com/v-grabko/GoJsBackend