[Из песочницы] Пишем 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

© Habrahabr.ru