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

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

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

В наличии

В данной статье рассматривается пример интеграции 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 ) и переписать все на С++.

 

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

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

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

has been added to your cart:
Оформление заказа