Пишем backend на go с минимальными усилиями при помощи GoJs

в 14:55, , рубрики: Go, golang, golang framework, javascript, javascript framework

Я видел кучу статей на эту тему (я думаю вы тоже видели и сейчас думаете, что это очередной шлак) и всё сводилось к описанию роута на каждый 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).

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

<html>
    <head>
        <!-- Подключим фреймворк-->
        <script src="/js/GoJs/main.js"></script>
        <!-- И запустим нашего зверя-->
        <script>
            main({
                //конфигурация
                controllers_dir: "/app/controllers",
                views_dir: "/app/views",
                gojs_dir: "/js/GoJs",
                ApiServer: "localhost:8080"
            }, 
            //говорим что при старте выполнить из контроллера start метод index
            "start@index", 
            //а в этой функции мы можем и обязаны кастомизировать что угодно. 
             function () {
             
            });
        </script>
    </head>
    <body>
    <!--В этот див шаблонизатор будет грузить результат своей работы -->
        <div id="page"></div>
    </body>
    </html>

Теперь в корне проекта создаём три директории после создания клонируем в /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}}<br>
{{var.data}}<br>
{{var.test}}<br>

А что, если нам необходимо выполнить несколько запросов к 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}}<br>
{{var.Testing}}<br>

Да, это работает. Но это, если честно, адский говнокод. Для этого создана абстракция, которая выполняет асинхронные запросы «синхронно». Вы наверное спросите: зачем городить велосипед, если можно использовать синхронные запросы? Всё очень просто. Они подвесят браузер.

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

Автор: VGrabko

Источник


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js