Integrar dos aplicaciones con un WebService SOAP

Una consulta que recibo bastante a menudo remite a cómo conectar dos sistemas dentro de una organización.

Algunos ejemplos:

Tengo la necesidad de conectar dos sistemas, uno de administración de stock y otro de asignaciones de bienes a personal. Lo que debería suceder son dos cosas:

1. Sistema de administración hace compra de stock, por lo cual deberá informarle al otro sistema que debe actualizar las cantidades.

2Sistema de asignación de bienes a personal hace entrega de un producto a un empleado, por lo cual deberá informarle al otro sistema que descuente del stock las cantidades otorgadas.

El problema principal que tengo es que no hay posibilidad de modificar el sistema de asignación por lo cual tengo que desarrollar los web services intermedios para poder comunicarme con él a través de archivos planos.

Tengo dos plataformas, las llamaré A y B.

A es una nueva plataforma web entre nuestros clientes y nosotros, con un formulario para configurar un objeto. (con una series de atributos)

B es el Product Data Management. Es la herramienta interna que usamos para gestionar estos objetos (crear/actualizar, etc…). Cada objeto también tiene una serie de atributos.

La idea es de recuperar información de la plataforma A y usarla para generar/actualizar automáticamente un objeto en B, usando los web services de B.

Creo que ya debe haber quedado claro que la problemática en todos los casos es similar.

Conectar dos sistemas no es precisamente una tarea trivial.

Más aún si están desarrollados usando diferentes tecnologías.

Más aún si están hosteados en diferentes redes.

En definitiva estamos en una de dos situaciones:

  • Un caso super sencillo en el que basta usar un recurso compartido (Un archivo, una base de datos, un repositorio de memoria, etc…).
  • Tenemos que pensar en una implementación basada en WebServices.

Es más, mi recomendación es que, aunque hoy la solución pueda basarse en un esquema simple, vale la pena hacer el esfuerzo extra para preparar la conexión de ambos sistema para otros escenarios menos favorables.

Al fin y al cabo, el esfuerzo tampoco será tan grande.

Qué se necesita para conectar dos sistemas mediante SOAP

Básicamente lo que se necesitará es implementar, al menos, un cliente y un servidor.

Esto no necesariamente tendrá un impacto elevado en cuanto a cambios en la infraestructura.

Ni siquiera tiene por qué suponer una modificiación sustancial en las aplicaciones aunque, para dar seguridad habría que ver qué tan bien estructurado está el código.

En definitiva, lo que se necesitará es dotar a la aplicación receptora de la información de un mecanismo para exponer un webservice SOAP y a la emisora de uno para consumirlo.

Esto puede sonar un poco contra-intuitivo, pero debe pensarse como que el receptor es quien brinda el servicio y el emisor quien lo utiliza a modo de cliente.

Un ejemplo de conexión via SOAP

Voy a tomar una versión simplificada del primer caso para realizar el ejemplo.

Asumo que contamos con una base de datos con esta estructura:

Sistema de compras
Sistema de asignación de productos

Dejo de lado una serie de detalles que, si bien son sumamente importantes, harían el ejemplo tan complejo que se perdería lo fundamental:

  1. No realizaré verificaciones de consistencia (De cantidades asignadas vs. compradas, de SKUs entre aplicaciones, etc…).
  2. Asumiré que la seguridad de la comunicación se maneja en alguna otra capa.

Ahora sí, comencemos.

Informando al sistema de asignación sobre nuevas compras

Desde el lado del sistema de compras, lo que se necesita es enviar información al sistema de asignación, para lo cual, éste debe estar preparado para recibirlos.

Para no entrar en un dilema de huevo-gallina, arranco por el lado del cliente asumiendo que el lado servidor está disponible y luego paso a mostrarlo.

El código del cliente se podría parecer a esto:

<?php

require_once 'db.config.php';

$mysqli = new mysqli($db_host, $db_user, $db_password, $db_name);

$sql = "SELECT pu.quantity, pr.SKU FROM purchases pu INNER JOIN products pr ON pr.id = pu.product_id;";

$reuslt = $mysqli->query($sql);

$client = new SoapClient('purchases.wsdl');

foreach ($reuslt->fetch_array(MYSQLI_ASSOC) as $purchaseRecord) {
    if ( 1 != $client->save_purchase(
        $purchaseRecord['SKU'],
        $purchaseRecord['quantity']
    ) ) {
         die ('Failed to save update of '.$purchaseRecord['SKU']);
    }
}

Este código podría ejecutarse dentro de una tarea cron, incorporarse como parte del proceso de registro de una compra o como respuesta a una acción específica del usuario.

Todo dependerá del contexto general en el que operen los sistemas en cuestión.

Asentando información de nuevas compras en el sistema de asignación

Del lado del servidor (el sistema de asignación en este caso), encontraríamos un código similar a:

<?php

function save_purchase(string $SKU, int $quantity) : int
{
    $sql = "UPDATE products SET stock = stock + ".$quantity." WHERE sku = '$SKU'";

    $config = require_once 'config.php';

$mysqli = new mysqli($config['db_host'], $config['db_user'], $config['db_password'], $config['db_name']);


    return $mysqli->query($sql) ? 1: 0;
}

$server = new SoapServer("purchase.wsdl");

$server->addFunction('save_purchase');
$server->handle();

Este código deberá estar alojado en alguna URL a la que pueda acceder el cliente, en este caso, el sistema de compras.

Y, en ambos lados se encontrará el archivo de definición del webservice que se verá similar a:

<!--
Leeway
2022-07-26
A service to record new product purchases
  -->
<definitions xmlns:tns="com.leewayweb.academy.wsdl" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
             xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsd1="com.leewayweb.academy.xsd"
             xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
             xmlns="http://schemas.xmlsoap.org/wsdl/" name="A service to record new product purchases"
             targetNamespace="com.leewayweb.academy.wsdl">
    <!--  definition of datatypes  -->
    <types>
        <schema xmlns="http://www.w3.org/2000/10/XMLSchema" targetNamespace="com.leewayweb.academy.xsd">
            <element name="SKU">
                <complexType>
                    <all>
                        <element name="value" type="string"/>
                    </all>
                </complexType>
            </element>
            <element name="quantity">
                <complexType>
                    <all>
                        <element name="value" type="int"/>
                    </all>
                </complexType>
            </element>
            <element name="result">
                <complexType>
                    <all>
                        <element name="value" type="int"/>
                    </all>
                </complexType>
            </element>
        </schema>
    </types>
    <!--  response messages  -->
    <message name="returns_result">
        <part name="result" type="xsd:result"/>
    </message>
    <!--  request messages  -->
    <message name="save_purchase">
        <part name="SKU" type="xsd:SKU"/>
        <part name="quantity" type="xsd:quantity"/>
    </message>
    <!--  server's services  -->
    <portType name="purchases">
        <operation name="save_purchase">
            <input message="tns:save_purchase"/>
            <output message="tns:returns_result"/>
        </operation>
    </portType>
    <!--  server encoding  -->
    <binding name="purchases_webservices" type="tns:purchases">
        <soap:binding style="rpc" transport="http://schemas.xmlsoap.org/soap/http"/>
        <operation name="save_purchase">
            <soap:operation soapAction="urn:xmethods-delayed-quotes#save_purchase"/>
            <input>
                <soap:body use="encoded" namespace="urn:xmethods-delayed-quotes"
                           encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/>
            </input>
            <output>
                <soap:body use="encoded" namespace="urn:xmethods-delayed-quotes"
                           encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/>
            </output>
        </operation>
    </binding>
    <!--  access to service provider  -->
    <service name="academy">
        <port name="academy_0" binding="purchases_webservices">
            <soap:address location="http://127.0.0.1/purchases.php"/>
        </port>
    </service>
</definitions>

Informando al sistema de compras sobre la insuficiencia de stock

Ahora entonces toca hacer algo muy similar pero visto desde el otro lado:

<?php

$client = new SoapClient("stock.wsdl");

$config = require_once 'config.php';

$mysqli = new mysqli($config['db_host'], $config['db_user'], $config['db_password'], $config['db_name']);

$sql = "SELECT SKU FROM products WHERE stock < minimum;";

$result = $mysqli->query($sql);

foreach ($result->fetch_array() as $product) {
    $product = $result->fetchArray();
    $client->generatePurchaseOrder($product['SKU']);
}

Este script podría, como vimos antes, ejecutarse periódicamente, o como resultado de una acción iniciada por el usuario (Lo que mejor se adapte a las necesidades del negocio).

Generando la orden de compra

Del lado del sistema de compras nos encontraremos con algo como:

<?php

function create_purchase_order(string $SKU) : int
{
    $sql = "INSERT INTO purchase_orders (product_id, quantity) SELECT id, purchase_quantity FROM products WHERE SKU = '$SKU';";

    $config = require_once 'config.php';
    $mysqli = new mysqli($config['db_host'], $config['db_user'], $config['db_password'], $config['db_name']);

    return $mysqli->query($sql) ? 1: 0;
}

$server = new SoapServer("stock.wsdl");

$server->addFunction('create_purchase_order');
$server->handle();

Y, no olvidar, el archivo WSDL:

<?xml version="1.0" encoding ="utf-8"?>
<!--
Leeway
2022-07-28
Creates pruchase orders for products below critical point
 -->
<definitions name="Creates pruchase orders for products below critical point"
             targetNamespace="com.leewayweb.wsdl"
             xmlns:tns="com.leewayweb.wsdl"
             xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
             xmlns:xsd="http://www.w3.org/2001/XMLSchema"
             xmlns:xsd1="com.leewayweb.xsd"
             xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/"
             xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
             xmlns="http://schemas.xmlsoap.org/wsdl/">
    <!-- definition of datatypes -->
    <types>
        <schema targetNamespace="com.leewayweb.xsd" xmlns="http://www.w3.org/2000/10/XMLSchema">
            <element name="SKU">
                <complexType>
                    <all>
                        <element name="value" type="string"/>
                    </all>
                </complexType>
            </element>
            <element name="resultcode">
                <complexType>
                    <all>
                        <element name="value" type="int"/>
                    </all>
                </complexType>
            </element>
        </schema>
    </types>
    <!-- response messages -->
    <message name='returns_resultcode'>
        <part name='resultcode' type='xsd:resultcode'/>
    </message>
    <!-- request messages -->
    <message name='create_purchase_order'>
        <part name='SKU' type='xsd:SKU'/>
    </message>
    <!-- server's services -->
    <portType name='purchase_orders'>
        <operation name='create_purchase_order'>
            <input message='tns:create_purchase_order'/>
            <output message='tns:returns_resultcode'/>
        </operation>
    </portType>
    <!-- server encoding -->
    <binding name='purchase_orders_webservices' type='tns:purchase_orders'>
        <soap:binding style='rpc' transport='http://schemas.xmlsoap.org/soap/http'/>
        <operation name='create_purchase_order'>
            <soap:operation soapAction='urn:xmethods-delayed-quotes#create_purchase_order'/>
            <input>
                <soap:body use='encoded' namespace='urn:xmethods-delayed-quotes'
                           encodingStyle='http://schemas.xmlsoap.org/soap/encoding/'/>
            </input>
            <output>
                <soap:body use='encoded' namespace='urn:xmethods-delayed-quotes'
                           encodingStyle='http://schemas.xmlsoap.org/soap/encoding/'/>
            </output>
        </operation>
    </binding>
    <!-- access to service provider -->
    <service name='academy'>
        <port name='academy_0' binding='purchase_orders_webservices'>
            <soap:address location='http://127.0.0.1./purchase_orders.php'/>
        </port>
    </service>
</definitions>

Resumiendo

Integrar dos sistemas puede ser una tarea compleja.

Mucho dependerá del conocimiento que se tenga sobre la organización de la información en cada uno de ellos, la infraestructura que les dé soporte y demás.

Independientemente de las dificultades técnicas que esto suponga, la integración automática de sistemas de información supone para muchas empresas una mejora sustancial en la eficiencia de sus procesos de negocio (Imagina lo que significa trasladar inforamción manualmente de una base de datos a otra).

Personalmente, antes de meter una capa extra de complejidad como la que supone utilizar SOAP intentaría diseñar una solución basada en REST.

Claro que, muchas veces no es posible tomar esta decisión porque otra persona ya lo hizo por nosotros.

En tal caso la solución pasará por utilizar las mejores herramientas de las que podemos disponer.

Para este ejemplo usé este sitio para generar el WSDL ya que se trataba de servicios simples, pero para un caso más realista intentaría usar una herramienta como esta.

mchojrin

Por mchojrin

Ayudo a desarrolladores PHP a afinar sus habilidades técnicas y avanzar en sus carreras

¿Te quedó alguna duda? Publica aca tu pregunta

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.