Добрый день, Хабр!

Я довольно часто начинаю утро с просмотра хабра и наконец решил внести свой вклад в данный процесс изучения интересного. Если всё сложится, то это первая моя статься из цикла применения языка GO на производственных системах. Я хочу рассказать некоторые тонкости создания приложений и серверов, удобство языка и быстроту разработки на нем. Возможно, для профессионалов, эта статья покажется скучной и не интересной, но читая литературу я не нашел общей картины решения производственных задач. Хотя большинство задач решаются однотипно. В этой статье я расскажу общие принципы построения сервера и в качестве примера я буду использовать сервер для валидации и просмотра почтовых индексов Почты Россия. Эта статья будет освещать более общие, методологические проблемы и тонкости с которыми я столкнулся при создании данной системы. Я не буду описывать настройки общего характера, таких в сети много, я только хочу акцентировать внимание на мелочах, которые мешали запуску проекта.



Вместо вступления


Читая Хабр, я нередко сталкивался с задачей валидации – проверки правильности ввода пользователя информации. Каждый автор решал эту проблему по-своему. Использовал внешний сервис или писал свой сервер. Я предлагаю ещё одно решение этой задачи – создание сервиса справочников. Справочники — это сервера которые содержат информацию определенного направления и которые можно выделить в отдельную, независимую подсистему. Обычно справочники используются для помощи пользователю в заполнении и для валидации введённой информации. В качестве примера таких справочников можно привести сервисы запросов регионов, запросов городов, КЛАДР, почтовых индексов, справочники ГАИС(государственные автоматизированные информационные системы). Почти все существующие системы осуществляют обмен данными по REST, используя или json или soap. Попробую рассказать шаблон для разработки таких справочников, который позволит быстро создавать такие системы. И выложить на github исходники моего внутреннего проекта.

Выбор систем. Сразу возникает вопрос почему GO? Почему Linux? И какой Linux?


И так начнём по порядку. Наша теперешняя система построена на продуктах от компании 1С, а именно БУС и корпоративный портал. Для унификации OS, мы выбрали из рекомендованного по установке 1С — а именно, OS Linux CentOS. Скрипты запуска веб-окружения 1С запускаются именно на этой OS. OS Windows не рассматривался. Не могу сказать, что выбор мне нравиться, я бы выбрал debian, но так сложилось. Унификация нам была необходима, ибо исторически у нас большой зоопарк OS, различные сборки OS Linux, OS FreeBSD, OS VxWorks. Да и, на мой взгляд, самый быстрый OS Linux CRUX.

Как многие проекты создаваемая система на 1С разрослась в большой портал и возникла необходимость выделить из системы и/или дополнить систему различными сервисами — справочниками. Анализируя предложения и возможности уже созданных сервисов, а также свои потребности в данных справочниках, мы пришли к выводу, что справочник:

  • Автономная служба-сервер;
  • Обмен данными происходит по REST;
  • Имеет возможность горизонтального масштабирования;
  • Должен быстро отдавать данные, хоть и избыточные, обработка на 1С продуктах;
  • Быстро перестраивать логику в зависимости от новых бизнес задач;

По вышеописанным критериям и своей простоте вхождения был выбран язык Go для написания справочников. Дополнительно:

  • Go язык с компилятором и позволяет включать фрагменты кода на C;
  • Программа это один файл со всеми библиотеками (нет проблем с совместимостью библиотек после сборки);
  • большое сообщество (много примеров и готовых решений);

То есть Go хорошо подходит для создания справочников. От себя добавлю, php и Go немного похожи, что позволяет поддерживать системы, написанные на этих языках. Так же на Go уже есть большое количество решений, которое позволяет быстро скомпоновать эти решения под свои задачи, то есть как в конструкторе из кубиков собрать модель дома. И так перейдем к практике.

GO. Установка и поиск библиотек или решений


Для установки необходимо скачать архив cо страницы загрузки и распаковать в папку установки, я буду использовать /usr/local.

wget https://dl.google.com/go/go1.12.1.linux-amd64.tar.gz
tar -C /usr/local -xvzf go1.12.1.linux-amd64.tar.gz

Хочу обратить внимание, Go разделен на две категории первая – сам язык компиляции и библиотеки включенные в сборку (GOROOT), вторая – это дополнительные библиотеки, которые Вы поставили и где будете создавать свой проект(GOPATH). Настройка окружения и подготовка папки разработки, я буду использовать /home/gouser/. Добавляем /etc/profile или в пользовательский .profile.

export GOPATH=/home/gouser
export GOROOT= /user/local/go/
export PATH=$PATH:/usr/local/go/bin

Подготавливаем папку для своего проекта:

mkdir -p /home/gouser/{bin,pkg,src}

Далее все просто для своего проекта создаем папку в src, создаем файлы с расширением go и компилим проект:

go build

Рекомендации по созданию проекта. Общие рекомендации


  • Для проекта на github понятнее создавать папку для проекта src/github.com/<имя проекта>;
  • проект оформлять в виде пакета, каждую процедуру комментировать – путь для очень удобного получения готовой программной документации в godoc;
  • Глобальные переменные вынести в отдельный модуль и подключать его при необходимости (напоминает шаблон одиночку);

И готовые примеры, и библиотеки можно найти на libs.garden. Установка найденных примеров

go get <ссылка на  пакет> , например github.com/labstack/echo

PostCode. Ссылка на проект


После установки Go перейдем к разбору примера. Скачать и установить для компиляции его можно командами:

go get github.com/julienschmidt/httprouter
go get github.com/LindsayBradford/go-dbf/godbf
go get github.com/go-sql-driver/mysql
go get github.com/julienschmidt/httprouter
cd  /home/gouser/go/src/github.com/
git clone https://github.com/Theo730/postcode.git postcode 

Постановка задачи


Необходимо создать справочник для работы с почтовыми индексами почтой России, который будет решать следующие задачи:

  • Валидация почтовых индексов;
  • Возможность получения списка всех объектов учета (регионов, готодов и тд.);
  • Получение всех индексов в объекте учета городов, районов, областей…
  • Из выше описанного, осуществлять обмен данными по REST в виде json.

Исследование задачи


Сначала нужно инициализировать базу, а потом создать сервер запросов к этой базе. Нужна база данных почтовых индексов. По поиску находится vinfo.russianpost.ru/database/ops.html. База в FoxPro и в zip архиве. В базе находятся следующие объекты учёта:

  • регион;
  • автономная облась;
  • район;
  • населенный пункт;
  • почтовый индекс.

Для инициализации на libs.garden находим компоненты и примеры работы с бд и зипом. В качестве маршрутизатора запросов берём httprouter. Подключаем в проект.
При исследовании файла БД было обнаружено, что не все объекты учета заданы, то есть в выборке присутствуют нулевые значения.

Решение и реализация


Объекты учета выстаиваю в иерархию при инициализации БД, если объект учета нулевой, то имя берется с вышестоящего. Идеология сервера – запросы приходят в main (тут роутер запросов) и перенаправляются в handlers (тут все проверки данных, конвертация и тд.). Из handlers осуществляются все запросы в БД и расчетов и handlers осуществляет вывод полученной информации. Такая декомпозиция позволяет разделить запросы в бд, расчеты, проверку введённой информации и вывод найденной. В принципе всё.

Вместо итога


Справочник по функционалу оказался сложнее КЛАДР. Он не только позволяет создать валидатор или справочник на сайте, но и создать алгоритм регионального бизнеса.

Если есть распределенный региональный бизнес продажи услуг или товаров. Дилеры в регионах регистрируются на портале, указывают область, где они будут оказывать услугу или продавать товар и им присваивается массив почтовых индексов. Клиент при запросе услуги или товара указывает почтовый индекс, и его заявка отправляется именно его региональному дилеру (тут можно выстроить сложный алгоритм). Список REST запросов есть на github в документации к проекту.

И немного Bitrix


Данный справочник можно подключить к любому проекту или framework-у, но так как у нас bitrix, я положил модуль для добавления валидатора в веб-формы стандартных компонент.

P.S. Инсталляция сервера специально разделена на 3 этапа. Это сделано из-за устаревания базы с vinfo.russianpost.ru сайта и из-за лицензионных ограничений. Сама база не является моей собственностью используя её вы принимаете лицензионную политику выше озвученного сайта.

P.S.S. Сервера созданные в моих проектах для корпоративного сегмента и по условиям не должны выходить в интернет. Код может модифицироваться под Ваши конкретные задачи. Мы не используем внешний доступ для наших проектов. Следующие проекты, которые я хотел бы описать – это полноценный КЛАДР и анализатор протокола потокового радио. На очереди Bitrix24 + asterisk. Прошу комментировать кому что интереснее.

Комментарии (10)


  1. Theo730 Автор
    25.03.2019 12:24

    Пожалуйста аргументируйте советы и критику.


  1. eltanweb
    25.03.2019 12:48

    «От себя добавлю, php и Go немного похожи, » — чтооооо?
    Посмотрел Ваш код, пройдите gotour сначала ваш код ужасен.
    Начиная с того, что при перезапуске сервиса вы удаляете таблицы и снова их создаете.


    1. Theo730 Автор
      25.03.2019 12:48

      Аргументируйте, что именно не так.

      Начиная с того, что при перезапуске сервиса вы удаляете таблицы и снова их создаете.
      — где это в моем коде? Сервис инициализируется один раз — при установке сервера. Далее при старте это простые обращения к БД и отображение результатов.


      1. eltanweb
        25.03.2019 13:05

        PidFile := postcode.Init(Versio) ->  err = InitMysql() ->   _, err = Db.Exec("DROP TABLE IF EXISTS citys ")
        


        что значит при установке? А если служба упала и надо рестартануть?

        P.S.
        Зачем вы так делаете не пойму. Что мешает писать нормально?
        str 		:= "(SELECT NAME FROM indexes WHERE TOP_ID in" 
            for i:= level-2; i>tLevel; i-- {
        	str		= fmt.Sprintf("%s (SELECT ID FROM %s WHERE TOP_ID in ", str, table[i])
            
            } 
            str			= fmt.Sprintf("%s (%s)", str, id)
            for i:= level-1; i>tLevel; i-- {
        	str		= fmt.Sprintf("%s )", str)
            } 
            fmt.Printf("%s\n",str)  


        Могу написать еще, но не вижу смысла. А так куча антипаттернов.


        1. Theo730 Автор
          25.03.2019 13:38

          ещЁ раз

          PidFile := postcode.Init(Versio) -> err = InitMysql() -> _, err = Db.Exec(«DROP TABLE IF EXISTS citys „)

          err = InitMysql() — этого вызова не происходит при рестарте службы

          и приведенный кусок кода формирует один sql, да не очень красиво, чистый sql


          1. eltanweb
            25.03.2019 13:50
            -1

            Ну если честно у Вас гавнокод. Не обижайтесь.
            Если считаете иначе, покройте данный код тестами.

            initdbPtr		:= flag.String("init", "", " - Path to file data, create database and init params")


            Это должна быть отдельная структура, не говоря уже о глобальных переменных (DB)


            1. Theo730 Автор
              25.03.2019 15:01

              Я не обижаюсь. Я стараюсь учится и понять как правильно. Да и Вы правы что глобальные переменные надо убирать, я сам написал в статье. Постараюсь переписать код, что бы он был удобочитаемым. Да и тесты я добавлю.


              1. Deathik
                25.03.2019 19:00

                Как человек, который тоже учит Golang и вообще современные ЯП — конкатенация строк через "+" не самое лучшее решение. В Го можно пользоваться fmt.Sprintf() для таких целей, а у вас много где.
                Но это, вполне возможно, субъективно.

                И у Го принято писать тесты прямо рядом с файликом основной программы в формате "*your_filename*_test.go" и тестировать с помощью пакета testing


                1. Theo730 Автор
                  25.03.2019 19:11

                  Спасибо, я конкатенацию строк в проекте переписываю на strings.Builder, как будет работать выложу, проект как пример компиляции кода(вставки с разных проектов), хочется узнать как правильно. И с тестирование базовую теорию я прочитал, хочется все стороне этот вопрос изучить.


                  1. Crafter76
                    27.03.2019 11:33

                    Многострочные строковые переменные, надо не в конкатенацию убирать, а грависом (``) обособлять