gifts2017

Интеграция 1С с сервисной шиной OpenESB

Опубликовал Андрей Межов (mini_root) в раздел Программирование - Практика программирования

В данной статье рассматривается пример интеграции 1С с шиной сервисов OpenESB, и в более широком смысле - встраивание 1С в сервис-ориентированную архитектуру.

Эта статья является, в некотором роде, продолжением статьи "Интеграция 1С с шиной сообщений MSMQ", в ней будет рассмотрен пример организации взаимодействия 1С с шиной ESB, и, в конечном счете, ее встраивания в сервис-ориентированную архитектуру (SOA).

 

Общие соображения

 

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

Идея была простая и вытекала из желания объединить воедино всевозможные преобразователи, внешние запускали и пр. Собственно для организации шины ESB вполне можно использовать тот же подход, что и при использовании обычной шины сообщений, таким образом двумя главными элементами будут: 
1. Некая промежуточная шина, которая позволяет принимать и передавать сообщения в согласованном формате.
2. Множества подключаемых модулей, которые принимают и передают сообщения - этими модулями могут быть коннекторы к внешнем системам или движок сервисов, получивший SOAP сообщение и переправляющий его движку BPEL (Busines Process Execution Language), который тоже оформлен в виде компонента. 
Собственно нижеприведенная схема организации JBI ( http://en.wikipedia.org/wiki/Java_Business_Integration ) это иллюстрирует:

jbi


Таким образом получаем модульную среду, позволяющую объединить самые разные компоненты и системы. Однако не стоит идеализировать ESB, как и в любом другом ПО там могут быть свои собственные ошибки, например, в ранних версиях OpenESB где-то в глубине вылетал NullPointerException при попытке вызвать сервис на Mono и усе, кина не будет.
 
Другим важным вопросом является движок веб-сервисов, его задачи когда-то были довольно простыми: принять SOAP, приземлить вызов, забрать результат и отдать SOAP. Собственно встроенный движок 1С и находится на таком уровне, но сейчас от движка требуется, помимо этого, реализация множества стандартов, определяющих то или иное поведение, например, WS-ReliableMessaging (для гарантированной доставки сообщений), WS-Security (для шифрования, подписи и аутентификации - заметьте, имено это стандарт, а не поделка 1С с HTTP аутентификацией, потому что веб-сервисы в общем случе могут быть и не "веб"), WS-[Atomic]Transaction (для организации транзакционного поведения). Очевидно, что чем более развитым и надежным является движок, тем лучше. Эти соображения, а также желания сделать универсальный механизм, который можно легко прикрутить к любой конфигурации, сразу же ставят крест на встроенном движке 1С. Собственно выбор не богат, но это не значит, что плох - WCF под .NET.

Схема работы

 

Основная идея уже всплывала в комменатариях к предыдущей статье: сделать универсальный windows сервис, который будет приземлять вызовы на 1С через COM.  Собственно схему работы всей конструкции можно выразить одной строкой:

    ? <-n SOAP n-> OpenESB <-1 SOAP 1-> OneCService(WCF) <-1 COM n-> 1С

где ? - произвольные внешние системы, в примере их роль играют тесты

     OneCService - собственно сервис, разработанный в рамках данного примера

     в угловых скобках указаны способы взаимодействия и отношения

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

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

Инструментарий

 

В качестве инструментов для создания примера понадобятся:

  1. 1С 8.1 (две базы с одинаковой конфигурацией, не связаннные планом обмена, имитирующие две разнородные системы)
  2. .NET 3.5 SP1 + Sharpdevelop 3.1 (можно заменить на VisualStudio)
  3. Netbeans 6.5.1 + OpenESB 2.1 + жаба современного образца.

 

Тонкости с типами

 

Забегая вперед, рассмотрим один тонкий момент: при любом взаимодействие всегда надо обеспечить передачу данных в формате, понятным обеим сторонам. В 1С большую часть работы в этом плане за нас сделает СериализаторXDTO. Но есть ряд тонкостей:


1. Получение информации о принадлежности данного конкретного значения тому или иному типу. 

    Казалось бы все просто, все пользовались ТипЗнч(<значение>),  но попробуйте выполнить простую строку:

        ТипЗнч("ЙЦУКЕН");

        ой, а это что такое:

         Встроенная функция может быть использована только в выражении. (ТипЗнч)
      
 ТипЗнч<>("ЙЦУКЕН");

       при попытке вызвать ее через COM эффект будет не лучше.

   Вот почему не получится полностью обойтись без модификации конфигурации, хотя типы можно получить и из результата запроса:
    ВЫБРАТЬ 1, "А", Истина, Дата(1,1,1)

в этом случае доработка не требуется.

2. Отсутствие сериализации в XML универсальных коллекций штатными средствами.
    А ведь так хочется сделать метод, который бы возвращал/принимал массив чего-то там, и дергать его из внешней системы...
    
Значит нужно обеспечить как минимум распознавание простых типов и универсальных коллекций (массив в данном примере) и их сериализацию/десериализацю. Со сложными типами проще - с элементом какого-нибудь справочника вполне управятся и штатные средства. При этом надо внести лишь минимальные дополнения в конфигурацию. Информация о типах потребуется в двух основных случаях - при формировании информации о типах колонок в ResultSet'е и при определении того, что значение относится к универсальной коллекции, все остальное будет отправляться в СериализоторXDTO.

При этом в примере реализовано сразу несколько способов определения типов, например, определить тип колонки в результате запроса при наличии образцов типов можно так:


 

public Type GetColumType(int _index)
       
{
            object currentType = GetProperty(
                                       
Invoke(GetProperty(result, "Колонки"), "Получить", new object[] {_index}),
                                       
"ТипЗначения"
                                           
);
            try
           
{
                if ((bool)Invoke(currentType, "СодержитТип", new object[] {doubleType}))
               
{
                    return typeof(double);
               
}
                else if ((bool)Invoke(currentType, "СодержитТип", new object[] {stringType}))
               
{
                    return typeof(string);
               
}
                else if ((bool)Invoke(currentType, "СодержитТип", new object[] {boolType}))
               
{
                    return typeof(bool);
               
}
                else if ((bool)Invoke(currentType, "СодержитТип", new object[] {dateType}))
               
{
                    return typeof(DateTime);
               
}
                else
               
{
                    return null;
               
}
            }
            finally
            {
                Release(currentType);
           
}
        }

 

В качестве примера использования, рассмотрим метод, возвращающий значение по индексу колонки, преобразованное в нужный формат:

 

public object GetValueByIndex(int _index)
{
if (GetColumType(_index) == null)
{
object o = Invoke(resultSet, "Получить", new object[] {_index});
               
o = GetObjectByRef(o);

                return
OneCObjectToXML(o);
}
else
{
                return Invoke(resultSet, "Получить", new object[] {_index});
}
}

Как видно из кода, все довольно просто: определяется тип, если он примитивный, то значение возвращается как есть, а если нет - вызвается метод сериализующий значение в XML, рассмотрим его:

 

public XmlElement OneCObjectToXML(object _o)
{
if (IsArray(_o))
{
XmlDocument doc = new XmlDocument();
               
XmlElement arrayElement = doc.CreateElement(OneCServiceArrayElement);
               
doc.AppendChild(arrayElement);
               
int count = (int)Invoke(_o, "Количество", new object[] {});
                for (
int i=0; i<count; i++)
               
{
                    object item = Invoke(_o, "Получить", new object[] {i});
                    if (
item != null)
                   
{
                        XmlElement itemElement = doc.CreateElement(OneCServiceArrayElement+"-item");
                       
arrayElement.AppendChild(itemElement);
                       
XmlElement itemValueElement = OneCObjectToXML(GetObjectByRef(item));
                       
itemElement.AppendChild(doc.ImportNode(itemValueElement, true));
                   
}
                }
                return doc.DocumentElement;
}
else
{
                object writeXml = Invoke(connection, "NewObject", new object[] "ЗаписьXML"});
                try
               
{
                    Invoke(writeXml, "УстановитьСтроку", new object[] {});
                   
Invoke(xdtoSer, "ЗаписатьXML", new object[] {writeXml, _o});
                   
//Заполнем буфер текстом xml представления 1с'овского объекта
                   
string xmlString = (string)Invoke(writeXml, "Закрыть", new object[] {});
                   
using (StringReader sr = new StringReader(xmlString))
                   
{
                        XmlDocument doc = new XmlDocument();
                       
doc.Load(sr);
                        return
doc.DocumentElement;
                   
}
                }
                finally
                {
                    Release(writeXml);
               
}
}
}


Как видно из кода, зесь же происходит обработка ситуации, когда надо сериализовать универсальную коллекцию.

Также в примере используется и другие способы определния типов (вплоть до использования "is"), какой из них лучше - вопрос спорный.

 

Реализация

 

Ну а теперь, не отвлекаясь, можно рассмотреть собственно сервис. Начнем с интерфейса:

 

    [ServiceContract(Name="onecservice", Namespace="http://onecservice")]
   
public interface IOneCWebService
    {
        [OperationContract(Name="ExecuteRequest")]
       
ResultSet ExecuteRequest(string _file, string _usr, string _pwd, string _request);

        [
OperationContract(Name="ExecuteScript")]
       
ResultSet ExecuteScript(string _file, string _usr, string _pwd, string _script);

        [
OperationContract(Name="ExecuteMethodWithXDTO")]
       
ResultSet ExecuteMethodWithXDTO(string _file, string _usr, string _pwd, string _methodName, XmlNode[] _parameters);
   
}

 

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

 

    public class Value : IXmlSerializable
    {
        private XmlNode anyElement;

       
public XmlNode AnyElement
        {
            get { return anyElement; }
            set { anyElement = value; }
        }

        public XmlSchema GetSchema()
       
{
            return null;
       
}

        public void ReadXml(XmlReader reader)
       
{
            XmlDocument document = new XmlDocument();
           
anyElement = document.ReadNode(reader);
       
}

        public void WriteXml(XmlWriter writer)
       
{
            anyElement.WriteTo(writer);
       
}
    }

    [DataContract(Namespace="http://onecservice/types")]
   
public class Row
    {
        private List<XmlNode> values = new List<XmlNode>();

       
public List<XmlNode> ValuesList
        {
            get {return values;}
        }

        [DataMember]
       
public XmlNode[] Values
        {
            get {return values.ToArray();}
            set {}
        }
    }

    [DataContract(Namespace="http://onecservice/types")]
   
public class ResultSet
    {
        private List<string> columnNames = new List<string>();
       
private List<string> columnTypes = new List<string>();
       
private List<Row> rows = new List<Row>();

       
private string   error = "";

        [
DataMember]
       
public string Error
        {
            get {return error;}
            set {error = value;}
        }

        [DataMember]
       
public List<string> ColumnNames
        {
            get {return columnNames;}
        }

        [DataMember]
       
public List<string> ColumnTypes
        {
            get {return columnTypes;}
        }

        [DataMember]
       
public List<Row> Rows
        {
            get {return rows;}
        }

        public ResultSet()
       
{
        }
    }

 

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

 

Рассмотрим также изменения, которые необходимо внести в конфигурацию 1С:

 

Функция ПринадлежитТипу(значение, тип) Экспорт
    Возврат
тип = ТипЗнч(значение);
КонецФункции

Функция
ВыполнитьСтроку(стр) Экспорт
   
результат = Неопределено;
    Выполнить
стр;
    Возврат
результат;
КонецФункции

 

Две базы 1С с соотвествующими изменениями в конфигурации и тестовым справочником приведены в архиве.

Таким образом у нас есть адаптированная конфигурация 1С и сервис, через который к ней можно достучаться откуда угодно.  Собственно, результаты полученные на данном шаге уже имеют самостоятельную ценность, так как позволяют, например, выполнить запрос аля ВЫБРАТЬ * Из ....  откуда угодно, например из веб приложения написанного на RoR'е.

Переходим к созданию так называемого композитного приложения, которое будет развернуто в OpenESB. В данное приложение будет входить один BPEL модуль, в котором будут располагаться несколько BPEL процессов, каждый из которых будет иллюстрировать работу с тем или иным методом сервиса OneCService. Там же будут располагаться wsdl, описывающий интерфейс OneCServic'а, а также wsdl'и описывающие точки входа BPEL процессов и xsd, описывающите типы данных. В композитном приложении будут созданы несколько тестов, которые сводятся к формированию запроса к тому или иному BPEL процессу и проверке на ожидаемый результат. Такая организация позволит разбить приложение на несколько небольших и понятных частей. Рассмотрим примеры этих частей более подробно.

 

В качестве примера BPEL процесса рассмотрим процесс, выполняющий запрос к 1С и возвращающий его результат, упакованный в ResultSet:

 

bpel

 

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

 

assembly

 

Теперь рассмотрим более сложный пример в комлпексе - обмен данными между двумя базами 1С. BPEL процесс имеет вид:

 

exchange

 

Как видим, здесь происходит сначала обращение к первой базе 1С ("ПолучитьСотрудников"), а потом ко второй ("ЗаписатьСотрудников"). Данные передаются в массиве, также здесь выполняется XSL преобразование чтобы извлечь массив из ResultSet'а, полученного в результате обращения к первой базе, для его последующей передачи во вторую базу.

 

Видео, иллюстрирущее работу схемы лежит здесь http://www.youtube.com/watch?v=NlvsvRpDf5o , в нем также демонстрируется пример пошаговой отладки BPEL процесса (пользуемся кнопочкой HD, так хоть что-то можно разглядеть).

Исходники композитного приложения также приведен в архиве.

 

Недостатки примера

1. Работа только с файловыми базой 1С.
2. Костыль для работы с типами (возможно я чего-то не понял).

 

Что можно улучшить

1. Добавить поддержку SQL баз.
2. Попытаться реализовать в поддержку транзакции средствами WCF и их приземление на 1С (не знаю насколько реально).
3. Возможно стоит переработать ResultSet.
4. Сделать возможность приземлять в 1С произвольные типы, объявленные во внешних схемах, добавленных в "Пакеты XDTO".

1C 8.2, Linux и Native API

Собственно соображение тут только одно: заменить WCF на Axis2/C ( http://ws.apache.org/axis2/c ) и переписать все на С++.

 

Архив лежит здесь: http://infostart.ru/projects/5333/


P. S. Как обычно, жду конструктивной критики и вопросов. А вообще, чем больше копаю, тем больше прихожу к выводу, что "адынэс рулЕз" :)

P.P.S. Для тех кто хочет заглянуть чуть-чуть в будущее: http://wiki.open-esb.java.net/attach/FujiScreenCastsDemos/Fuji-overview-0629.pdf  , хотя Фуджи пока на редкость глюкава и неустойчива. 

См. также

Подписаться Добавить вознаграждение
Комментарии
1. Андрей Межов (mini_root) 30.07.09 23:53
ээээх... опять все разъехалось...
2. Dmitry Afanasyev (afanasko) 31.07.09 16:44
Очень интересная статья. Но, блин, скриншоты и видео на тубе мелковаты. Ничего не разглядеть ). Не мог бы ты выложить все это где-нибудь в более высоком разрешении?
3. Dmitry Afanasyev (afanasko) 31.07.09 16:49
Ступил. На Тубе можно же в fullscreen и в HD смотреть :-)
4. Андрей Межов (mini_root) 31.07.09 16:53
Вообще, основным наглядным материалом задумывалось видео, а скриншоты, чтобы сориентировать на что в видео обращать внимание.

Постараюсь сделать скриншоты побольше.
5. Андрей Межов (mini_root) 06.08.09 10:39
мдя.... прошла неделя, три десятка скачиваний архива.... и не одного вопроса или попытки обсуждения

6. Евгений Заручейский (zarucheisky) 06.08.09 18:12
Вы слишком много хотите от аудитории.
Я вот тоже сейчас ковыряю ESB и пришел к выводу, что для скрещивания
1С с любой ESB (будь то mule, OpenESB, wso2 или коммерческие), надо хорошо продумать представление данных, или... использовать родной формат, как в сообщениях обмена. Более того, можно создать нормальную удобоваримую xsd-схему типов, поскольку встроенное представляет собой нечто ужасное и неудобоваримое.
7. Евгений Заручейский (zarucheisky) 06.08.09 18:13
Ну и опять же, OLE... только слезли с этих граблей...
8. Андрей Межов (mini_root) 07.08.09 11:43
(6) Вопрос о формате/структуре данных он всегда первый, при любом обмене... Я склоняюсь к тому, чтобы использовать штатную сериализацию 1С, орудуя XSL'ем для преобразования. Да и сторонние типы, прописанные в XSD в 1С вроде бы можно прокинуть.

(7) А таки есть альтернатива? По-моему уж лучше OLE + WCF чем встроенный движок веб сервисов 1С (я там прошлой зимой тааакие грабли видел....). Да и производительность поднять чуток можно, если на стороне сервиса сделать пул поднятых V8.Application (сохранив, разумеется, проверку пользователя и пароля, и установив ограничения по максимальному размеру пула). Хотя конечно, более точно можно будет сказать только после того, как устрою всему этому нагрузочные испытания.

Ну и наконец, никто не мешает сделать индейскую хитрость - для большого потока данных (а это прежде всего обмен между 1С'ами по плану обмена) использовать обычную шину сообщений с каким-нибудь промежуточным ретранслятором/преобразователем (если потребуется), а для особо запущенных случаев, для координации и мелкого обмена, другими словами, для вещей которые обычно выходят за план обмена - ESB.

А аудитория тут нормальная, просто немного вялая и не задававшаяся такими вопросами. И "адынэс рулЕз" на полном серьезе, особенно по сравнению с аналогами.

P.S. Собственно, если в 8.2 будет нормальный движок сервисов и нормальное разведение по подсистемам типовых конфигураций, можно будет обойтись без всех этих извращений - сделать какую-нибудь универсальную нашлепку к той же торговле, и готово.
9. Евгений Заручейский (zarucheisky) 07.08.09 15:17
(8)
к (6)
Правильно. Идеология ESB именно и состоит в том, что бы приложение не занималось преобразованием, но. Тогда транслятору в ESB (например, xalan) надо выдать схему данных и т.п. для преобразования. И на самом деле всё гораздо сложней.

(7) Спорно. Опять же, 1С реализовало урезанное ws-поверх http со всеми отсюда вытекающими последствиями. Конечно, велик соблазн использовать встроенный механизм сервисов. Однако, и это можно объехать используя другие методы и средства, например ADODB в связке с MS SQL Server 2008 Service Broker (MOM от Microsoft). Собственно, способов связать 1С с ESB много.
Вопрос только пристрастий. Я не сторонник множественных сессий, особливо в клиент-серверном варианте, может привести в некоторых случаях к утечке памяти и т.п.

10. Андрей Межов (mini_root) 07.08.09 15:55
(9) ну так схема типов 1С замечательно экспортируется в XSD, а вот в чем действительно проблема, так это в написании сложных преобразований в XSL, особенно если используются расширения - там черт ногу сломит.

К тому же к OpenESB есть MSMQ адаптер вроде бы, а как организовать работу плана обмена через шину сообщений я в предыдущей статье уже описал ( http://infostart.ru/blogs/1178/ ). Вариантов безусловно множество - у всех есть свои плюсы и минусы.

Чем на мой взгляд хорош тот вариант который я здесь рассмотрел, так это тем, что количество телодвижений минимальное: написал функцию ПолучитьСотрудников, написал процедуру ЗаписатьСотрудников - и гоняй данные. По сути получился универсальный адаптер к 1С, который можно использовать откуда угодно. Более того, в огромном количестве случаев можно обойтись методом ExecuteRequest и значениями примитивных типов в ResultSet'е (все задачи по вытаскиванию данных из 1С).

А для больших объемов все же наверное лучше использовать шины сообщений (MOM'ы - уже упомянутый мелкомягкий, всевозможные JMS'ы), разбивая поток на мелкие сообщения.


P.S. Как вылетает ошибка при одновременном поднятии большого количества экземпляров V8.Application я видел, но где-то до десятка вполне нормально работают.
11. Евгений Стоянов (quick) 11.01.10 18:07
Тоже меня сначала плющило на SOAP, но в итоге пришел к простоте xml-rpc
http://infostart.ru/public/20253/
Повезло с питоном в том что проблем с кодировками никаких не возникло.
12. Андрей Межов (mini_root) 12.01.10 09:22
(11) Ну для меня это не вариант, потому что хочется чего-то большего да и с SOAP'ом проблем особых не возникало. Однако в твоем примере прежде всего заинтересовало то что он для 77, из описания не совсем понятно возможна ли передача произвольных аргументов, и, что самое интересное, возвращение произвольных значений. Халявной сериализации-то в 77 нету...

Свою идею планирую развивать:
http://infostart.ru/forum/forum19/topic30245/
13. Evgeny Melnikov (Melnikovbk) 21.06.12 12:35
mini_root

собственно идет интеграция 1с с помощью веб-сервисов
мне из 1с необходимо дернуть процесс написанный на BPEL лежащий на JBoss вроде
но 1с обваливается с Краш ошибкой при импорте схемы ВСДЛ Бипла

есть ли у вас работающий сервис на бипле который вызывается из 1с и можно ли посмотреть ВСДЛку?
14. Андрей Межов (mini_root) 10.07.12 10:44
Нет, такого примера у меня сейчас нет. Надо смотреть, что за WSDL генерит JBoss и избавляться от любых наворотов в нем, вроде включенного WS-Security, надо смотреть как в этом WSDL'е описаны сложные типы (они могут и в отдельном XSD быть) и пр.

Да и вообще, я бы на движок веб-сервисов 1С'а не сильно полагался - там даже относительно "взрослые" движки, вроде WCF/Metrо/Axis2/CXF между собой далеко не всегда дружат - попытки заставить работать CXF и WCF между собой с шифрованием и подписью закончились пару лет назад безрезультатно (у меня), хотя вроде и стандарт, и xml'ники правильные формируются, и шифруется/подписывается, и каждый из движков лично самого себя прекрасно понимает и расшифровывает...
15. Виталий Трач (vitalya24) 18.04.13 15:44
Тема очень стоящая прочтения.
Сам сейчас занимаюсь интеграцией сервисной шини на базе MS Biz Talk.
Я как интегратор со стороны 1с.
Все работает на веб сервисах.
16. Михаил Ражиков (tango) 18.04.13 15:49
читал, читал...
столько много буков, чтоб на ASP-страничке заделать COM к 1ске?