От экспертов «1С-Рарус»: «1С» в облаке AWS и использование сервиса AWS Lambda
От экспертов «1С-Рарус»: «1С» в облаке AWS и использование сервиса AWS Lambda

От экспертов «1С-Рарус»: «1С» в облаке AWS и использование сервиса AWS Lambda

10.09.2020
49 мин
35625

Концепция совместного использования «1С» и Amazon Web Services

На одном из проектов в Юго-Восточной Азии в соответствии с ИТ-стратегией клиента все ИТ-системы должны быть размещены в облаке Amazon Web Services. Это требование коснулось и разрабатываемой нами системы управленческой отчетности 1C:Management Information System.

В основном мы используем облако AWS как IaaS — наши виртуальные сервера развёрнуты в частном облаке AWS VPC на базе сервиса AWS EC2.

Virtual Private Cloud

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

Концепция совместного использования 1С и Amazon Web Services

Как следствие, в нашей системе применяется большое количество различных механизмов интеграции.

Наиболее интересной получилась интеграция с MS SQL с использованием бессерверных сервисов AWS Lambda и Amazon API Gateway.

Концепция совместного использования 1С и Amazon Web Services

Кластер MS SQL развернут в сервисе Amazon Relational Database Service (Amazon RDS) и мы извлекаем данные из специализированной копии основной базы данных — реплики чтения (Read Replica). Применение реплики чтения позволяет снизить нагрузку на основную (Master Primary) базу данных.

Доступ к ней должен осуществляться в соответствии с требованиями международного стандарта по информационной безопасности ISO 27001.

Именно эти требования заставили отказаться нас от подключения напрямую к этой MS SQL базе как к внешнему источнику данных «1С» и привели нас к использованию API Gateway и Lambda-функций для интеграции.

Amazon API Gateway — это полностью управляемый сервис для разработчиков, который упрощает публикацию, обслуживание, мониторинг, защиту и использование API. Сервис API Gateway позволяет создавать API RESTful, используя API HTTP или API REST, при этом управлять входящим трафиком (запросами) путем установки правил ограничения нагрузки на основании количества запросов в секунду для каждого HTTP-метода в API.

AWS Lambda — это сервис бессерверных вычислений, который запускает программный код в ответ на определенные события и отвечает за автоматическое выделение необходимых вычислительных ресурсов.

В нашем случае такими событиями были запросы от «1С». Притом, запросы были как достаточно простыми с достаточно быстрым получением результатов, так и сложными, которые требовали продолжительного времени для выборок и вычислений. Именно второй тип запросов потребовал разработки асинхронного метода работы для некоторых «тяжелых» запросов.

Реализация функциональной части Lambda

Выбор языка для разработки функций

AWS Lambda позволяет запускать программный код без выделения серверов и управления ими. Языки программирования в настоящее время делятся на 3 основных типа: интерпретируемые, компилируемые и JIT-компилируемые (Just-In-Time).

Различие между интерпретируемыми и компилируемыми языками заключается в способе обработки исходного кода. В первом случае специальная программа (интерпретатор) по очереди выполняет все инструкции программы, преобразовывая каждую из них в машинный код и немедленно выполняя. Во втором случае, компилятор обрабатывает весь текст программы сразу, сохраняя результат в виде файла, готового для последующего многократного выполнения.

JIT (Just-In-Time) компиляторы соединяют в себе оба подхода. Сначала компилятор преобразует инструкции языка программирования в промежуточный байт-код и сохраняет его в файл, затем при исполнении этого файла байт-код преобразуется в инструкции машинного кода и выполняется.

Сервис AWS Lambda имеет встроенную поддержку:

  • Интерпретируемых (скриптовых) языков — Go, PowerShell, Python, Ruby, Node.js).
  • JIT-компилируемых — Java, C# (.Net Core).

Кроме того, сервис предоставляет API среды выполнения для создания функций с использованием любых других языков программирования, в т. ч. компилируемых. Для использования этого API необходимо предварительно подготовить среду выполнения, т. е. некий программный код, способный выполнить разработанную Lambda-функцию. Среда выполнения загружается в сервис вместе c Lambda-функцией.

В случае использования скриптовых языков AWS Lambda дает возможность редактирования исходного кода.

Выбор языка для разработки функций

В качестве языка для разработки функций был выбран C# (.Net Core Framework), поскольку в платформе есть встроенная библиотека для доступа к базам данных MS SQL, а также  имелся опыт разработки приложений на этой платформе. В качестве альтернативы рассматривался Python, однако для подключения к серверам MS SQL требуется компиляция и установка драйверов ODBC для Linux, что, по отзывам, могло вызвать затруднения.

Организация инфраструктурного окружения для разработки

При разработке функций использовалась среда разработки Visual Studio Code для Windows. Эта среда мультиплатформенная, поэтому все нижеизложенное верно и для операционных систем Linux и MacOS.

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

Устанавливаем расширение AWS Toolkit для VS Code.
Устанавливаем расширение C# for Visual Studio Code.

Для удобного управления сервисом AWS Lambda из командной строки, в т. ч. из терминала VS Code, установим набор инструментов, который позволяет управлять сервисом с рабочего места разработчика.

Устанавливаем набор инструментов командной строки AWS Command Line Interface (https://docs.aws.amazon.com/cli/latest/
userguide/install-cliv2-windows.html).

Далее устанавливаем средства интеграции dotnet и AWS. Это позволит собирать функции и публиковать их в сервис с помощью командной строки dotnet.

В командной строке устанавливаем инструменты AWS для dotnet:

dotnet tool install -g Amazon.Lambda.Tools

Устанавливаем шаблоны проектов AWS:

dotnet new -i Amazon.Lambda.Templates

Для публикации функций в AWS c помощью инструментов командной строки необходимо добавить учетные данные пользователя AWS в файл %USERPROFILE%\.aws\credentials на рабочем месте разработчика.

Создаем новый проект из шаблона Lambda-функции:

dotnet new lambda.EmptyFunction --name Functions --profile defalut --region ap-southeast-1

Открываем папку проекта в VS Code, на этот момент уже есть проект для разработки функций.

Организация инфраструктурного окружения для разработки

Для хранения исходных кодов и управления версиями в проекте используется ПО Gitlab (https://gitlab.com). Кроме контроля версий, данная система имеет возможность реализации полной цепочки Continuous Integration/Continuous Development (CI/CD), а также богатые возможности интеграции с различными системами сборки и тестирования ПО, управления конфигурациями и контейнерными средами.

Создаем новый проект в web-интерфейсе gitlab (не путать с проектом VS Code):

Организация инфраструктурного окружения для разработки

VS Code имеет встроенные возможности взаимодействия  c git, как в графическом интерфейсе, так и во встроенном терминале. Инициализируем репозиторий, выполнив в терминале команду:

git init

Организация инфраструктурного окружения для разработки

Подключаем его к проекту на gitlab:

git remote add  origin https://mygitlaburl/awslambda 

Далее мы можем добавить измененные файлы в индекс (stage), зафиксировать изменения (commit) и сохранить (push) в репозиторий на gitlab.

Структура проекта

В процессе разработки были реализованы основные классы проекта, представленные на диаграмме.

Структура проекта

Согласно требованиям архитектуры AWS-Lambda, для каждой функции должен быть указан метод-обработчик, являющийся точкой входа в эту функцию, принимающий входные параметры и возвращающий результат.

Класс FunctionHandles содержит набор таких методов-обработчиков для реализованных Lambda-функций. Эти методы, в свою очередь, вызывают методы внутренних классов, выполняющие конкретные операции.

Для взаимодействия с MS SQL разработан класс DBConnector, который имеет единственный обобщенный метод RunQuery, предназначенный для выполнения запросов к базе данных.

Класс ConnectionFactory предоставляет объект соединения с конкретной базой данных.

SQL-запросы к БД, в зависимости от ситуации, могут использовать различные выражения фильтрации, поэтому для их хранения реализовано 2 класса — Queries, который содержит неизменяемые части запросов, и Filters, в котором хранятся выражения фильтрации.

В проекте используются 2 типа Lambda-функций — синхронные и асинхронные. Их способы обработки внешних вызовов отличаются, однако способ получения данных у них общий, поэтому для реализации всех обработчиков разработано 3 класса:

  1. LambdaBaseHandler — набор методов и свойств, общих для всех функций. Формирует запросы к БД, передает их на обработку в класс DBConnector и возвращает результат.
  2. LambdaSyncHandler — наследник LambdaBaseHandler, содержит свойства и методы, специфичные для синхронных функций. Обрабатывает входные параметры, вызывает необходимые методы базового класса, преобразует результат и возвращает его.
  3. LamdaAsyncHandler — наследник LambdaBaseHandler, выполняет те же задачи, что и класс LambdaSyncHandler для асинхронных функций.

Для обмена информацией с клиентской частью проекта, разработаны классы моделей, представляющие сущности предметной области БД. На диаграмме представлена одна из таких моделей — Airport.

Окончательный проект имеет следующий вид:

Структура проекта

Классы проекта для удобства сгруппированы в папки в соответствии с их функционалом.

В папке Db хранятся классы, обеспечивающие взаимодействие с базой данных. В папке Handlers находятся классы, реализующие точки входа в Lambda функции, преобразование входных и выходных данных, вызовы методов взаимодействия с базой данных. Папка models содержит классы моделей сущностей предметной области.

Загрузка функций на AWS

Для загрузки функций в сервис AWS Lambda исходный код проекта необходимо скомпилировать в программные модули (dll) и упаковать в файл .zip. Для этого используем расширение lambda для dotnet CLI:

dotnet-lambda package

В папке .\bin\Release\netcoreapp3.1\ будет создан файл Functions.zip, содержащий все программные модули и зависимости проекта.

В web-консоли управления сервисом AWS Lambda создаем новую функцию, указав имя и среду исполнения:

Загрузка функций на AWS

Открываем настройки функции, в разделе Function code загружаем файл Functions.zip:

Загрузка функций на AWS

В разделе Basic settings указываем Handler в формате assembly::namespace.class-name::method-name, где:

  • assembly — полное наименование программного модуля .net;
  • namespace.class-name — наименование пространства имен и класса, которому принадлежит обработчик функции;
  • method-name — собственно метод, вызываемый при запуске функции.

Кроме того, в сервисе IAM необходимо создать роль, обладающую разрешениями, необходимыми для запуска Lambda-функций, и выбрать её в выпадающем списке Existing role.

Cервис AWS Identity and Access Management (IAM) предоставляет возможности безопасного управления доступом к сервисам и ресурсам AWS. Используя IAM, можно создавать пользователей AWS и группы, управлять ими, а также использовать разрешения, чтобы предоставлять или запрещать доступ к ресурсам AWS.

Для публикации функции в AWS также можно использовать расширение AWS для dotnet CLI, выполнив команду:

dotnet lambda deploy-function
В процессе выполнения нужно будет указать ту же информацию, что и при создании функции в web-интерфейсе.

В сервисе AWS Lambda используется событийная модель архитектуры. В этой модели для активации приложений и сервисов используются события.

Событийная модель имеет три ключевых компонента — поставщик событий (event producer), маршрутизатор событий (event router) и потребитель событий (event consumer).

События — структуры данных определенного формата — формируются поставщиком и передается маршрутизатору, который их фильтрует и передает соответствующим потребителям. Облако AWS поддерживает множество различных событий — запросы API Gateway, записи логов CloudWatch, сообщения SNS и другие.

В нашем случае поставщиком события является сервис API Gateway, он же и маршрутизирует события — запросы к сервису, устанавливая соответствия между внешними http-запросами и Lambda-функциями, которые являются потребителями событий.

Для возможности активации функции с помощью внешних событий необходимо подключить один или несколько возможных триггеров — поставщиков событий.

Загрузка функций на AWS

В нашем случае используется API Gateway, который можно подключить к существующему API или создать новое. В результате мы получим ссылку, используя которую можно активировать нашу Lambda-функцию извне.

Загрузка функций на AWS

Отладка функций

Отладка, как процесс локализации и исправления ошибок, предполагает обдумывание и логическое осмысление всей имеющейся информации об ошибке.

Даже при отладке обычных, «монолитных» приложений, этот процесс может отнимать довольно большое количество времени. В случае же бессерверных приложений трудоемкость отладки еще более возрастает из-за сложности архитектуры, в которой может быть задействовано множество различных сервисов.

Отладка функций в проекте происходила в несколько этапов. На этапе раннего тестирования было разработано обычное web-api к локальной базе данных в тестовой среде. Это позволило быстро отладить SQL-запросы, разработать методы получения и обработки данных, определить конфигурацию внешних точек вызова функций.

В частности, на этом этапе выяснилось, что для некоторых функций передаются большие массивы входных параметров, которые не укладываются в ограничения GET-запроса, что привело к использованию запросов POST и изменению порядка обработки запросов к базе данных, поскольку количество параметров запроса также превышало ограничение.

При разработке собственно Lambda-функций, использовались локальные методы тестирования и отладки, наиболее простой из которых — отладка в виде консольного приложения, внутри которого вызываются методы Lambda-функций с передачей им входных параметров.

Второй способ — это использование инструмента AWS .NET Mock Lambda Test Tool (https://github.com/aws/aws-lambda-dotnet/tree/master/Tools/
LambdaTestTool), который рассмотрим более подробно.

Для установки выполняем в окне терминала VS Code:

 dotnet tool install -g Amazon.Lambda.TestTool-3.1

Переходим в интерфейс отладки, в выпадающем списке справа от кнопки «Start debugging» выберем «Add configuration». В окне редактора откроется файл launch.json, приведем его  к следующему виду. Параметр «program» содержит путь к установленному инструменту отладки.

{
    // Use IntelliSense to learn about possible attributes.
    // Hover to view descriptions of existing attributes.
    // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0",
    "configurations": [
        
        {
            "name": ".NET Core Launch (console)",
            "type": "coreclr",
            "request": "launch",
                  "preLaunchTask": "build",
            "program": "${env:USERPROFILE}/.dotnet/tools/dotnet-lambda-test-tool-3.1.exe",
            "args": [],
            "cwd": "${workspaceFolder}",
            "console": "internalConsole",
            "stopAtEntry": false
        },
        {
            "name": ".NET Core Attach",
            "type": "coreclr",
            "request": "attach",
            "processId": "${command:pickProcess}"
        }
    ]
}

Сохраняем файл и запускаем отладку. Получим ошибку «Could not find the task ‘Build’». В окне ошибки нажмем кнопку «Configure task», выберем «Create tasks.json from template», далее выберем шаблон «NET Core». В окне редактора откроется файл tasks.json, приведем его к следующему виду:

{
    // See https://go.microsoft.com/fwlink/?LinkId=733558
    // for the documentation about the tasks.json format
    "version": "2.0.0",
    "tasks": [
        {
            "label": "build",
            "command": "dotnet",
            "type": "shell",
            "args": [
                "build"
            ],
            "group": "build",
            "presentation": {
                "reveal": "silent"
            },
            "problemMatcher": "$msCompile"
        }
    ]
}
public string FunctionHandler(string input, ILambdaContext context)

Запускаем отладку. В окне браузера откроется интерфейс AWS .NET Mock Lambda Test Tool.

Отладка функций

Инструмент позволяет сформировать необходимые входные параметры и запустить выполнение функции, при этом в VS Code можно использовать все стандартные средства отладки — точки останова, инспекцию переменных и т. д.

Отладка функций в продуктивной среде, т. е. непосредственно в облаке AWS представляет собой более сложную задачу. В данном проекте для отладки использовались встроенные возможности логирования, предоставляемые сервисом AWS.

Обработчик Lambda-функции может принимать в качестве аргумента объект контекста вызова с интерфейсом ILambdaContext.

public string FunctionHandler(string input, ILambdaContext context)

protected void LogMessage(string message)
        {
            context.Logger.LogLine(string.Format("{0}:{1} - {2}", context.AwsRequestId, context.FunctionName, message));
        }
    }

Объект контекста предоставляет информацию об окружении функции, например, её имя, версию, лимит доступной памяти и др., а также объект Logger. Пример его использования приведен выше.

Функция LogMessage принимает строковое сообщение и записывает его в систему логирования, добавляя идентификатор вызова AWS и имя функции. Таким образом возможно отслеживать выполнение ключевых операций функции, проверять значения переменных.

Записи лога можно посмотреть в сервисе CloudWatch.

Отладка функций

Устройство функций

Для получения доступа к БД MS SQL используется библиотека Microsoft.Data.SqlClient, которая является поставщиком данных платформы .NET для SQL, а также библиотека ORM (Object-Relational Mapping) Dapper (https://github.com/StackExchange/Dapper).

ORM (Object-Relational Mapping, объектно-реляционное отображение) — технология программирования, которая связывает базы данных с концепциями объектно-ориентированных языков программирования. Эта технология позволяет работать с таблицами баз данных как с классами языка программирования.

Библиотека Dapper была разработана программистами проекта StackOverflow и предоставляет набор методов расширений для классов поставщиков данных .NET ADO. Она может использоваться не только с MS SQL, но и с SQLite, Firebird, Oracle, MySQL, PostgreSQL и др. Dapper уступает «большим» ORM (например, Entity Framework, NHibernate) по функционалу, однако превосходит их по быстродействию.

В данном проекте используется обобщенный метод расширения Query<T>, который принимает текст SQL-запроса и преобразует результат в модель данных.

Пример SQL-запроса для функции GetAirports:

SELECT 
    SUBSTRING(Airports.str_Ident, 1, 3) AS Code,
    Airports.str_Name AS Description,
    Airports.lng_Airport_Id_Nmbr AS PSS_ID
FROM
    tbl_Airport AS Airports

В качестве триггера для Lambda-функций был выбран API Gateway, имеющий ограничение времени выполнения вызываемого кода, максимальное значение которого составляет 30 сек. В процессе тестирования выяснилось, что не все функции успевают отработать за этот интервал, поэтому было принято решение часть функций выполнять асинхронно, передавая результат методом обратного вызова.

Синхронные функции интегрируются с API Gateway c помощью Lambda Proxy, что позволяет работать внутри функции с «сырым» запросом и также формировать ответ. В случае асинхронного вызова функций Lambda Proxy недоступен, поэтому необходимо настраивать внешнюю интеграцию с API Gateway.

Разберем устройство синхронной функции на примере GetAirports.

public APIGatewayProxyResponse GetAirportsHandler(APIGatewayProxyRequest request,   
ILambdaContext context)
{
       
var Handler = new LambdaSyncHandler( context);
       return Handler.ProcessRequest(Queries.GetAirports, request.Body, Filters.AirportsFilterByKeys, Filters.AirportsFilterCommon);
}

Функция принимает объект request класса APIGatewayProxyRequest в качестве входного параметра, его свойство body содержит набор параметров запроса в виде JSON.

Далее создается экземпляр класса внутреннего обработчика запросов LambdaSyncHandler, который используется для обработки всех синхронных запросов, и выполняется его метод ProcessRequest.

public APIGatewayProxyResponse ProcessRequest(string qry, string param, string keysFilter, string commonFilter = "", string defaultFilter = "")
{
LogMessage("Request processing started");
int statusCode;
string body;
T1[] searchKeys = ParseBody(param);
try
{
                //Вызов метода конкретного класса-обработчика
  body = JsonConvert.SerializeObject(GetDataByKeysArray(qry, searchKeys, 
keysFilter, commonFilter, defaultFilter);

      statusCode = (int)HttpStatusCode.OK;
      LogMessage("Processing request succeeded.");
}
catch (Exception e)
{
       statusCode = (int)HttpStatusCode.InternalServerError;
       body = e.Message;
  LogMessage(string.Format("Processing request failed - {0}", JsonConvert.SerializeObject(e)));
 }
return CreateResponse(statusCode, body);
}

Это обобщенный метод, позволяющий одинаковым образом обрабатывать параметры различных классов. В качестве входных параметров он принимает строку основного запроса qry, json-строку param, строки keysFilter, commonFilter, defaultFilter, которые используются для формирования выражения WHERE SQL — запроса. Параметр класса T указывает класс объекта, возвращаемого методом, а T1 — класс входных параметров.

Строка param преобразуется в массив параметров нужного типа, в данном случае string, затем происходит вызов метода базового класса GetDataByKeysArray, который возвращает набор объектов класса T, в данном случае Airport. Далее происходит преобразование этого набора в строку JSON (body) и сборка результата в методе CreateResponse. В случае ошибки в переменную body записывается сериализованный объект Exception.

В методе GetDataByKeysArray создается объект класса DBConnector и вызывается его метод RunQuery.

public IEnumerable RunQuery(string qry,T1[] searchKeys, string keysFilter, string commonFilter = "", string defaultFilter = "")
        {
            List result = new List();

            string filter = commonFilter;

            using (var conn = _connectionFactory.GetSQLConnection())
            {
                if ((searchKeys != null) && (searchKeys.Length > 0))
                {
                    filter += keysFilter;

                    qry += filter;
			  //преобразование массива параметров в массивы по 2000 
                    T1[][] SearchKeysSplited = searchKeys
                  //проецирование исходного массива в набор анонимых объектов
                  .Select((s, i) => new { Value = s, Index = i })
                  //группировка по полю Index по 2000 шт.
			.GroupBy(x => x.Index / 2000)
                   //преобразование каждой группы в массив
                   .Select(grp => grp.Select(x => x.Value).ToArray())
                   //преобразование набора массивов в массив
                   .ToArray();

                    foreach (var keys in SearchKeysSplited)
                    {

                        var DapperParam = new Dictionary();

                        DapperParam.Add("searchkeys_param", keys);

                        
                        result.AddRange(conn.Query(qry, DapperParam, commandTimeout: _commandTimeout));
                    }
                }
                else
                {
                    filter += defaultFilter;
                    qry += filter;

result.AddRange(conn.Query(qry, new Dictionary(), commandTimeout: _commandTimeout));
                }
            }
            return result;
       }

В этом методе происходит сборка SQL запроса из основного текста и дополнительных фильтров WHERE, обработка параметров запроса и его выполнение.

Поскольку в данном случае действует ограничение на количество параметров (2100) в SQL-запросе, параметры обрабатываются последовательно, в нескольких запросах. Для разделения параметров на группы по 2000 применяется технология LINQ.

LINQ (Language-Integrated Query) предоставляет простой и удобный язык запросов к источнику данных. В качестве источника данных может выступать объект, реализующий интерфейс IEnumerable (например, стандартные коллекции, массивы), набор данных DataSet, документ XML. Но вне зависимости от типа источника LINQ позволяет применить один и тот же подход для выборки данных.

В нашем случае используется LINQ to objects с лямбда-синтаксисом (не имеет отношения к AWS Lambda, просто похожие названия) на массиве.

//преобразование массива параметров в массивы по 2000 
 T1[][] SearchKeysSplited = searchKeys
 //проецирование исходного массива в набор анонимых объектов
 .Select((s, i) => new { Value = s, Index = i })
 //группировка по полю Index по 2000 шт.
        .GroupBy(x => x.Index / 2000)
 //преобразование каждой группы в массив
 .Select(grp => grp.Select(x => x.Value).ToArray())
 //преобразование набора массивов в массив
 .ToArray();

В результате получаем двумерный массив, с элементами не более 2000 параметров.

Обрабатывая в цикле каждый из этих массивов, выполняем запросы к БД.

foreach (var keys in SearchKeysSplited)
{
var DapperParam = new Dictionary();
DapperParam.Add("searchkeys_param", keys);
result.AddRange(conn.Query(qry, DapperParam, commandTimeout: _commandTimeout));

Обобщенный метод Query является расширением для класса SqlConnection, подключаемым ORM Dapper и позволяет автоматически преобразовывать сущности БД в модели проекта. Полученные данные отправляются обратно по стеку и возвращаются в API Gateway в свойстве body объекта APIGatewayProxyResponse.

Сериализация и десериализация объектов происходят автоматически с помощью класса Amazon.Lambda.Serialization.
Json.JsonSerializer, подключенного на уровне программного модуля.

[assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.Json.JsonSerializer))]

Рассмотрим отличия асинхронных методов от синхронных.

Во-первых, функции, вызываемые из AWS API Gateway асинхронно, не могут использовать Lambda Proxy, поэтому необходимо настраивать интеграцию.

Во-вторых, данные, полученные из БД, необходимо отправлять в 1С, подключаясь к http-сервису. Также, для обеспечения взаимодействия с 1С и идентификации пакетов данных необходимо принимать параметр BatchID из заголовка запроса и возвращать его в ответе.

Интеграция Lambda-функций с API Gateway настраивается в web-консоли управления сервисом API Gateway.

Устройство функций

Для преобразования входящих запросов в параметры Lambda-функций применяются шаблоны преобразования с использованием Velosity script (https://ru.wikipedia.org/wiki/
Apache_Velocity).

Устройство функций

В этом шаблоне входные параметры для Lambda-функции извлекаются из тела запроса и, вместе c BatchID передаются на вход метода, где десериализуются в объект SearchKeysRequest<string> (в случае функции GetCharges).

Обработчик асинхронной функции имеет следующий вид:

public void GetChargesHandler(SearchKeysRequest req, ILambdaContext context
{
var Handler = new LambdaAsyncHandler(context);
try
{
          Handler.ProcessWithCallBack(req.SearchKeys, Queries.GetCharges,        Filters.ChargesFilterByKeys);
}
catch (Exception e)
{
    context.Logger.LogLine("ERROR:" + JsonConvert.SerializeObject(e));
}
}

Работа синхронных функций в общем не отличается от работы асинхронных, за исключением способа отправки данных клиенту. Синхронные функции отправляют их с помощью API Gateway, асинхронные функции используют для этого метод SendData.

public void SendData(string data, IDictionaryheaders=null) {
try{
   using (var cli = new HttpClient()){
      cli.Timeout = TimeSpan.FromMinutes(30);
//авторизация Basic
var byteArray = Encoding.ASCII.GetBytes(_callbackAuthUser + ":" +_callbackAuthPass);
//добавляем заголовок авторизации
cli.DefaultRequestHeaders.Authorization = 
new System.Net.Http.Headers.AuthenticationHeaderValue(
"Basic", Convert.ToBase64String(byteArray));
// подготовка http message
var msg = new HttpRequestMessage(HttpMethod.Post, _callbackUri);
msg.Content = new StringContent(data, Encoding.UTF8, "application/json");
//добавляем в заголовки название функции
msg.Headers.Add("function", FunctionName);
//добавляем в заголовки внешние данные (BatchID)
if(headers != null){
    foreach (var header in headers) {
       msg.Headers.Add(header.Key, header.Value);
    }
}
//Отправляем
using (var res = cli.SendAsync(msg).GetAwaiter().GetResult()){
    LogMessage("Callback response code: ." + res.StatusCode.ToString());
}
}
}
catch (Exception e){
   LogMessage("Data sending error:" + JsonConvert.SerializeObject(e));
}
}

Вызов функций Lambda из «1С»

Перед нами стояла задача реализовать 17 интеграций, к каждой из которых мы обращались через http-запрос по предоставленному URL. Изначально это были GET-запросы, но вскоре мы столкнулись с ограничением GET-запроса, когда необходимо было передавать большой массив параметров, такие проблемные интеграции мы начали переделывать на POST, со временем, для единообразия, перевели на POST все интеграции.

Так выглядит вызов HTTP-запроса в общем модуле, к которому обращаются все интеграции.

HTTPConnection = New HTTPConnection(Host, Port,,,,, New OpenSSLSecureConnection);
ResourceAddress = MethodName + GETParameters;

Headers = New Map;
If BatchID <> Undefined Then
	Headers.Insert("BatchID", "" + BatchID);
EndIf;

If POSTParameters = Undefined Then
	HTTPRequest = New HTTPRequest(ResourceAddress, Headers);
	HTTPResponse = HTTPConnection.Get(HTTPRequest);
Else
	Headers.Insert("Content-Type", "application/json");
	HTTPRequest = New HTTPRequest(ResourceAddress, Headers);
	HTTPRequest.SetBodyFromString(POSTParameters);
	HTTPResponse = HTTPConnection.Post(HTTPRequest);
EndIf;
	
ResponseString = HTTPResponse.GetBodyAsString();
  • BatchID — уникальный идентификатор для логирования процесса выполнения операций в нашей системе, для учета отправленных, принятых, обработанных запросов. Оказался крайне полезен при асинхронных запросах как способ хранения инструкций для данных, которые мы ожидаем получить от AWS через Web-service «1C».
  • POSTParameters — Параметры для принимающей стороны. В нашем случае в формате JSON с использованием стандарта ISO 8601 для обозначения дат (yyyy-MM-ddThh:mm:ss).

Остальные переменные используются стандартно для подобных операций:

  • Host — Строка — Хост сервера, с которым осуществляется соединение.
  • Port — Число — Порт сервера, с которым осуществляется соединение.
  • ResourceAddress — Строка — Адрес ресурса, к которому будет происходить HTTP-запрос. Прим.: getBalance?Currency=USD.
  • Headers — Соответствие — Содержит заголовки запроса в виде соответствия «Название заголовка» — «Значение».

В случае успешного выполнения в теле ответа возвращается JSON, содержащий требуемый набор данных.

[
	{
		"Code": "CTS",
		"Description": "New Sapporo",
		"PSS_ID": "110"
	},
	. . .
	{
		"Code": "DAT",
		"Description": "Datong Yungang",
		"PSS_ID": "121"
	}
]

После проведения первого цикла тестирования, выяснилось, что только 10 интеграций возвращают результат, остальные 7 завершаются с ошибкой по 30-ти секундному таймауту API Gateway (описанному в разделе «Реализация функциональной части Lambda: Устройство Функций»). Тогда, в качестве варианта было предложено реализовать асинхронные вызовы для «тяжелых» интеграций.

Вызов функций Lambda из «1С»

Вызов функций Lambda из «1С»

Для «1С» это выглядит следующим образом:

Отправлялся POST-запрос, также как обычно, но отклик содержал не данные, а уведомление «Запрос принят, обрабатывается».

Был разработан и опубликован на веб-сервере HTTP-сервис:

Вызов функций Lambda из «1С»

По мере готовности, обратная функция обращалась к «1С», передавая ей запрошенный набор данных. Т. е. все тоже самое, только отложено, отправив запрос «1С» не находится в ожидании ответа, ответ приходит сам извне.

Поскольку отправка запроса и получение данных никак с собой не связаны, а при получении ответа «1С» необходимо знать ответ на какой запрос пришел, в качестве параметра вызова асинхронной функции всегда передается BatchID (УИД), который присваивается «1С» и содержится в обратном отклике.

Примеры асинхронного и синхронного вызовов в бизнес-процессах пользователей

Синхронный вызов на примере GetAirports

Функция возвращает таблицу аэропортов в несколько сотен строк, поэтому при исправных каналах по всей архитектуре гарантировано и быстро возвращает результат, даже если запрашивается вся таблица.

В нашем решении, если не передать параметры, означает, что необходимо вернуть все данные, если параметры заполнены, то вернутся данные по переданным ключам.

Синхронный вызов на примере GetAirports

Для синхронных вызовов по запросу динамически формируется таблица, содержащая полученные данные.

Асинхронный вызов на примере GetPSSCheckInData

В качестве параметров передаются Дата начала, Дата окончания, BatchID.

Асинхронный вызов на примере GetPSSCheckInData

Для асинхронных вызовов отображается фрагмент «Лога импорта данных» (описан ниже), с указанием в какие объекты конфигурации (справочники, документы, регистры сведений) загружены данные и детальным описанием событий, происходивших в процессе обработки.

Лайфхаки

Обработка для тестирования и отладки

Или, как ее назвали «1С» Postman.

Перед тем как использовать полученные данные по назначению предстоит множество итераций совместного тестирования и отладки, внесения изменений.

Чтобы облегчить этот процесс практически для всех интеграций мы делали обработки, которые можно параметризовать и визуально отобразить полученные наборы данных. Хорошей практикой стало включение этих обработок в состав конфигурации, т. к. ими пользуются и другие члены команды, в частности, для того, чтобы ответить на вопросы:

  • Внесены изменения в состав передаваемых нам данных, как бы это посмотреть?
  • Какие еще данные есть, которые мы могли бы использовать?
  • Как выглядят «сырые» данные, которые мы получаем?

Лог импорта данных

Для целей анализа во всех интеграциях мы предусмотрели подробный лог импорта данных, который поможет дать ответ на вопрос «Что пошло не так?».

Представляет собой журнал с иерархическим списком событий. Можно просматривать как полностью, так и контекстно перейдя из созданного объекта.

Лог импорта данных

Очереди загрузки данных

Идея родилась по результатам реализации асинхронных функций, чтобы не «заваливать» сторонний сервер множеством запросов, вызовы выстраиваются в очередь — каждый следующий запрос отправляется после того как отработал предыдущий.

В частности, планируется использовать очереди при пакетной загрузке данных, когда, например, необходимо загрузить данные за год (по дням) для трёх интеграций, в очередь выстроятся 3×365 задания в порядке приоритета интеграции и возрастания дат.

Заключение

Мир меняется. Одной из целей статьи являлось показать изменение мира, ну и конечно снабдить инструкцией о том, как жить в изменяющемся мире.

Кроме инструкции как таковой важно понимать, что пространство, в котором живёт на сегодняшний день «1С» существенно расширило свои границы. И изменение системы вещей предъявляет новые требования к пониманию архитектуры «1С», окружающей ее инфраструктуры, безопасности, администрированию. Не говоря уже о критически важной потребности осваивать новые области знаний и практик.

Уход в облака, как постоянно наблюдаемая тенденция в отрасли, приводит к тому, что «1С» перестает быть изолированной экосистемой, тесно начинает сотрудничать с DevOps специалистами, программистами C#, Python и других языков.

Если вы хотите больше узнать о проекте, к которому относится эта статья — посмотрите доклад Дмитрия Шитова
«1С в облаке Amazon Web Services» с 1C-RarusTechDay 2020.

Авторы статьи

Александр Спиркин
Александр Спиркин
Роман Богацкий
Роман Богацкий
Дмитрий Шитов
Дмитрий Шитов
Андрей Черанёв
Андрей Черанёв
Ирек Ильясов
Ирек Ильясов
Вы читаете статью из рубрики:
От экспертов «1С-Рарус»

Есть вопросы по статье? Задайте их нам!

Рассылка «Новости компании»: узнавайте о новых продуктах, услугах и спецпредложениях

Посмотреть все рассылки «1С‑Рарус»

Поле не должно быть пустым
Электронная почта указывается только латиницей, обязательно должен присутствовать знак @, доменное имя не может быть короче двух символов

Посмотреть все рассылки «1С-Рарус»

Иконка «Предупреждение» Отправляя эту форму, Вы соглашаетесь с Политикой конфидециальности и даете согласие на обработку персональных данных компанией «1С-Рарус»

Заинтересованы в сотрудничестве?
Нужна консультация?
Свяжитесь с нами!