Search
Write a publication
Pull to refresh

Пишем backend на go с минимальными усилиями

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

Но всё не так сладко, как кажется. К примеру, сейчас почему-то в Опере мини из контролера не вызывается вьюшка.

github.com/v-grabko/GoJs
github.com/v-grabko/GoJsBackend
Tags:
Hubs:
You can’t comment this publication because its author is not yet a full member of the community. You will be able to contact the author only after he or she has been invited by someone in the community. Until then, author’s username will be hidden by an alias.