Arquitectura orientada a servicios

A medida que los sistemas de software crecen, aparece una tensión difícil de evitar: cada aplicación necesita resolver sus propios problemas, pero muchas de las capacidades que necesita ya existen en algún lugar de la organización. Una aplicación de ventas necesita consultar clientes, una aplicación de facturación necesita conocer esos mismos clientes y diferentes procesos necesitan consultar productos, contratos o pedidos. Si cada aplicación implementa de nuevo esas capacidades, terminamos multiplicando lógica, datos y reglas que deberían mantenerse coherentes. El problema deja de ser únicamente cómo construir una aplicación y pasa a ser cómo construir capacidades que puedan ser utilizadas por diferentes aplicaciones sin que cada una tenga que conocer cómo están implementadas.

La arquitectura orientada a servicios (Service-Oriented Architecture, SOA) surge precisamente como una forma de abordar este problema. Su idea central consiste en organizar el software alrededor de servicios que representan capacidades concretas y que pueden ser utilizados por diferentes consumidores. Un servicio encapsula una responsabilidad y expone una interfaz mediante la cual otros sistemas pueden utilizarla sin depender directamente de su implementación. La propia definición de servicio en el contexto de la arquitectura de software enfatiza precisamente su carácter reutilizable y la ocultación de su implementación respecto al consumidor. servicio en arquitectura de sistemas

Esto cambia nuestra forma de pensar sobre una aplicación. En lugar de imaginarla como un bloque que contiene toda la funcionalidad necesaria para resolver un problema concreto, podemos verla como una composición de capacidades proporcionadas por diferentes servicios. Para que esta idea funcione aparecen varias cuestiones fundamentales: qué debe convertirse en servicio, qué contrato debe ofrecer, cómo se combinan varios servicios y cómo se implementan sin que los consumidores queden acoplados a sus detalles internos.

Del software como aplicación al software como conjunto de capacidades

En una aplicación tradicional es habitual que las funcionalidades se organicen pensando principalmente en las necesidades de esa aplicación. Si necesitamos gestionar clientes, creamos un módulo de clientes; si necesitamos facturar, creamos un módulo de facturación. Esta organización puede funcionar perfectamente mientras tengamos una única aplicación o mientras cada funcionalidad tenga un único consumidor. El problema aparece cuando varias aplicaciones necesitan resolver las mismas capacidades.

Imaginemos una empresa con tres aplicaciones: un portal comercial, una aplicación interna para empleados y una aplicación móvil. Las tres necesitan consultar información de clientes. Una posibilidad sería que cada aplicación tuviera su propio código para acceder a los datos y aplicar las reglas correspondientes. Otra sería crear una capacidad común de gestión de clientes que pudiera ser consumida por las tres. En este segundo caso, el servicio de clientes se convierte en una pieza reutilizable de la arquitectura y las aplicaciones dejan de necesitar conocer directamente cómo se almacenan o procesan esos datos.

                    ┌──────────────────┐
                    │ Portal comercial │
                    └────────┬─────────┘
                    ┌────────▼─────────┐
                    │ Servicio clientes│
                    └────────┬─────────┘
                    ┌────────▼─────────┐
                    │ Datos y reglas   │
                    │ de clientes      │
                    └──────────────────┘
              ┌──────────────┴──────────────┐
              │                             │
     ┌────────┴─────────┐         ┌─────────┴────────┐
     │ App empleados    │         │ Aplicación móvil │
     └──────────────────┘         └──────────────────┘

La arquitectura orientada a servicios lleva esta idea más lejos que la simple reutilización de código. El objetivo es identificar capacidades con una responsabilidad propia, establecer una frontera alrededor de ellas y permitir que diferentes aplicaciones las utilicen mediante una interfaz conocida. La tecnología utilizada para exponer esa interfaz puede variar; lo importante es la relación entre consumidores y proveedor.

Qué convierte una funcionalidad en un servicio

No toda función o método de una aplicación debería convertirse en un servicio. Si hacemos que cada pequeña operación sea accesible remotamente, podemos acabar construyendo una arquitectura excesivamente fragmentada, en la que realizar una operación sencilla requiere múltiples comunicaciones entre componentes. El servicio debe representar una capacidad que tenga sentido como unidad de responsabilidad y que pueda ser utilizada independientemente de una aplicación concreta.

Por ejemplo, CalcularPrecioConDescuento puede ser una operación interna del servicio de pedidos. En cambio, GestionarClientes puede representar una capacidad más amplia que tenga consumidores en diferentes aplicaciones. Del mismo modo, en una empresa logística podríamos disponer de servicios relacionados con clientes, contratos, envíos o facturación. La frontera no viene determinada por el número de métodos ni por el tamaño del código, sino por la responsabilidad que la capacidad representa.

Esto también permite distinguir un servicio de una base de datos compartida. Si tres aplicaciones acceden directamente a las mismas tablas, están compartiendo datos, pero cada una puede interpretar esos datos y aplicar reglas diferentes. Cuando utilizamos un servicio, en cambio, podemos encapsular tanto los datos como las reglas necesarias para operar sobre ellos. El consumidor solicita una capacidad y no necesita conocer cómo se obtiene el resultado.

El contrato de servicio: acordar qué se ofrece

Si un servicio va a ser utilizado por diferentes consumidores, necesitamos establecer qué ofrece y cómo se utiliza. Aquí aparece uno de los conceptos fundamentales de SOA: el contrato de servicio. El contrato define la interfaz que permite a consumidores y proveedores ponerse de acuerdo sobre la comunicación: operaciones disponibles, datos intercambiados, errores y otras condiciones necesarias para utilizar el servicio.

Podemos imaginarlo como un acuerdo entre dos partes. El consumidor necesita saber qué puede pedir y qué recibirá, mientras que el proveedor se compromete a mantener ese comportamiento aunque cambie su implementación interna. El contrato establece la frontera entre lo que el servicio promete y la forma concreta en que consigue cumplirlo.

Por ejemplo, un servicio de clientes podría exponer una operación conceptual como ObtenerCliente. El consumidor necesita conocer qué identificador debe proporcionar y qué estructura recibirá, pero no debería necesitar saber si el servicio consulta una base de datos SQL, un sistema antiguo, una API externa o una combinación de varias fuentes.

Esta separación resulta especialmente importante cuando el servicio evoluciona. Si diez aplicaciones dependen de él, modificar su comportamiento deja de ser un cambio local. Por eso los contratos necesitan evolucionar de forma controlada y, cuando sea necesario, incorporar mecanismos de versionado. La reutilización aumenta el valor de un servicio, pero también hace más importante mantener estable aquello que sus consumidores esperan.

Abstracción: ocultar la implementación

El contrato nos lleva directamente a la abstracción. Cuando una aplicación consume un servicio, no debería necesitar conocer todos los detalles de su implementación. Si utilizamos un servicio de facturación, por ejemplo, el consumidor necesita saber cómo solicitar la creación de una factura y qué resultado obtendrá, pero no debería conocer las tablas donde se almacenan las facturas, el algoritmo utilizado para calcular los impuestos o el mecanismo empleado para comunicarse con otros sistemas.

Esta separación permite cambiar la implementación sin obligar necesariamente a cambiar a los consumidores. Podemos migrar una base de datos, sustituir un componente interno o introducir una nueva regla manteniendo intacto el contrato externo, siempre que el comportamiento esperado siga siendo compatible. La abstracción convierte la implementación en un detalle del proveedor y el contrato en el punto de encuentro entre las partes.

Sin embargo, abstraer no significa esconderlo todo. Un contrato demasiado genérico puede resultar difícil de utilizar y uno demasiado específico puede terminar exponiendo detalles internos. El nivel adecuado es aquel que proporciona al consumidor la información necesaria para utilizar la capacidad sin convertir la estructura interna del proveedor en parte de la interfaz.

Composición de servicios: construir algo mayor

Una vez que tenemos servicios con contratos definidos aparece una posibilidad especialmente interesante: combinarlos. La composición de servicios consiste en utilizar diferentes servicios para construir una capacidad o proceso de mayor nivel. Una aplicación no tiene por qué implementar desde cero cada una de las capacidades que necesita si puede aprovechar otras que ya existen.

Imaginemos un proceso de compra. Para completarlo podemos necesitar consultar el cliente, comprobar el stock, calcular el precio, registrar el pedido, realizar el pago y solicitar el envío. En lugar de concentrar toda esa lógica en una única aplicación, podemos disponer de diferentes servicios especializados y combinarlos para representar el proceso completo.

                  ┌─────────────────┐
                  │ Proceso de      │
                  │ compra          │
                  └────────┬────────┘
             ┌─────────────┼──────────────┐
             │             │              │
             ▼             ▼              ▼
       Servicio       Servicio       Servicio
       Clientes       Inventario      Pagos
             │             │              │
             └─────────────┼──────────────┘
                    Servicio de envío

La composición puede ser sencilla, como una aplicación que llama secuencialmente a varios servicios, o puede involucrar mecanismos específicos para coordinar el proceso. En ambos casos, una capacidad compleja puede construirse a partir de capacidades más pequeñas que ya existen en la organización.

Esto no elimina la complejidad, sino que cambia su naturaleza. Si una operación depende de cinco servicios, ahora existen cinco posibles puntos de fallo y diferentes comunicaciones que gestionar. La arquitectura debe decidir qué ocurre cuando una dependencia no responde, cómo se recupera una operación y qué parte del proceso es responsable de coordinar el conjunto.

Orquestación y coreografía: cómo se coordina un proceso

Cuando varios servicios participan en un proceso aparece una cuestión adicional: ¿quién decide qué servicio debe ejecutarse y en qué orden? Una posibilidad es utilizar una orquestación, donde existe un componente responsable de coordinar el proceso. El orquestador conoce la secuencia general: primero consulta al servicio de clientes, después solicita la reserva al servicio de inventario, posteriormente solicita el pago y finalmente inicia el envío.

                    ┌─────────────────┐
                    │  Orquestador    │
                    └───────┬─────────┘
            ┌───────────────┼────────────────┐
            │               │                │
            ▼               ▼                ▼
        Clientes        Inventario          Pagos
            │               │                │
            └───────────────┼────────────────┘
                          Envíos

Otra posibilidad es la coreografía (choreography), donde los servicios reaccionan ante determinadas situaciones y desencadenan la siguiente parte del proceso sin que exista necesariamente un coordinador central que conozca toda la secuencia. Este modelo tiene relación con la comunicación basada en eventos, aunque SOA y arquitectura basada en eventos no son conceptos equivalentes. Un sistema orientado a servicios puede utilizar llamadas síncronas, eventos o una combinación de ambos mecanismos.

La orquestación hace que el flujo global sea más explícito, pero concentra conocimiento del proceso en un componente. La coreografía reduce esa centralización, aunque puede dificultar la comprensión del proceso cuando intervienen muchos participantes. La elección depende de cómo queramos representar y gobernar el proceso, no de una regla universal.

Implementación de servicios: convertir el contrato en una capacidad real

Detrás del contrato existe una implementación concreta. El servicio necesita ejecutar la lógica de negocio, acceder a los datos que controla, comunicarse con otros sistemas cuando sea necesario y exponer el contrato a sus consumidores.

Una implementación podría estar compuesta por una API HTTP, una capa de aplicación, componentes de dominio y una base de datos. Pero SOA no exige una estructura interna única. Un servicio puede estar construido con .NET, Java u otra tecnología; puede utilizar una base de datos relacional o NoSQL; puede ejecutarse en un servidor tradicional, una máquina virtual o un contenedor. La arquitectura de servicios define principalmente la frontera y la forma de colaboración, no una receta concreta para el código interno.

              CONTRATO DEL SERVICIO
                ┌─────────────┐
                │ API /       │
                │ interfaz    │
                └──────┬──────┘
                ┌─────────────┐
                │ Lógica de   │
                │ negocio     │
                └──────┬──────┘
                 ┌─────┴─────┐
                 ▼           ▼
             Datos       Otros servicios

Esta separación explica por qué compartir una base de datos entre aplicaciones no equivale a compartir un servicio. Si varias aplicaciones acceden directamente a las mismas tablas, conocen detalles internos del sistema que las posee. Si acceden mediante un servicio, la estructura interna puede cambiar siempre que el contrato siga siendo compatible. La frontera arquitectónica está en la responsabilidad y en la interfaz, no simplemente en dónde reside físicamente el código.

Un ejemplo completo: construir una aplicación comercial a partir de servicios

Supongamos que una empresa dispone de varios sistemas independientes y quiere construir una nueva aplicación comercial. Esta aplicación necesita mostrar información de clientes, consultar productos, calcular precios, crear pedidos y solicitar envíos. En lugar de implementar todas esas capacidades dentro de la nueva aplicación, la organización decide aprovechar servicios existentes.

La aplicación solicita información al servicio de clientes, consulta el catálogo y utiliza el servicio de precios para calcular el importe correspondiente. Cuando el usuario confirma la operación, utiliza el servicio de pedidos y posteriormente solicita el pago y el envío.

                        APLICACIÓN COMERCIAL
             ┌─────────────────┼──────────────────┐
             │                 │                  │
             ▼                 ▼                  ▼
       Servicio de        Servicio de       Servicio de
        Clientes           Catálogo            Precios
             │                 │                  │
             └─────────────────┼──────────────────┘
                        Servicio de Pedidos
                         Servicio de Pagos
                        Servicio de Envíos

La nueva aplicación no necesita conocer las bases de datos de cada sistema ni implementar de nuevo las reglas que esos servicios ya proporcionan. Su responsabilidad consiste en componer las capacidades disponibles para construir la experiencia que necesita el usuario. Una segunda aplicación, como una aplicación móvil, podría reutilizar buena parte de esos mismos servicios.

Si el servicio de precios cambia su implementación interna y pasa, por ejemplo, de consultar una tabla de precios a utilizar un nuevo motor de promociones, la aplicación comercial puede seguir funcionando mientras el contrato conserve el comportamiento esperado. El ejemplo muestra así la diferencia entre reutilizar código y reutilizar capacidades: lo que se comparte no es la implementación, sino una responsabilidad accesible mediante un contrato.

Servicios, APIs y microservicios: conceptos relacionados, pero diferentes

En la práctica, estos conceptos suelen mezclarse. Una API puede ser el mecanismo mediante el cual exponemos un servicio, pero una API y un servicio no son exactamente lo mismo. La API describe una interfaz de acceso; el servicio representa una capacidad que puede utilizarse. Podemos tener diferentes mecanismos de acceso a una misma capacidad sin cambiar necesariamente la responsabilidad que representa.

Algo similar ocurre con los microservicios. La arquitectura de microservicios comparte muchas ideas con SOA, pero SOA y microservicios no son sinónimos. Microsoft describe SOA como un enfoque en el que una aplicación se estructura mediante diferentes servicios y señala que los microservicios derivan de SOA, aunque presentan diferencias importantes en aspectos como autonomía, despliegue y organización. Arquitectura orientada a servicios — Microsoft Learn

Los microservicios suelen llevar más lejos la independencia de cada servicio: cada uno puede tener su propio ciclo de vida, desplegarse de manera independiente y, habitualmente, controlar sus propios datos. SOA es un concepto más amplio y menos prescriptivo. Puede existir una arquitectura orientada a servicios sin que cada servicio sea un microservicio ni que sea necesario adoptar todas las prácticas asociadas actualmente a este estilo.

La distinción es importante porque evita utilizar una tecnología o estilo de despliegue como sustituto del razonamiento arquitectónico. Primero identificamos las capacidades y sus fronteras; después decidimos cómo exponerlas y qué tecnología utilizar para implementarlas.

El papel de la gobernanza

Cuando tenemos pocos servicios, mantenerlos coordinados puede ser relativamente sencillo. Cuando una organización tiene decenas o cientos de servicios utilizados por múltiples aplicaciones, aparecen otras preguntas: quién es responsable de cada servicio, cómo se versionan los contratos, qué consumidores existen, qué dependencias hay y cuándo puede retirarse una versión.

Aquí aparece la gobernanza de servicios. Su objetivo es mantener una visión coherente del ecosistema: identificar servicios, responsabilidades, propietarios, contratos, consumidores y ciclos de vida. La gobernabilidad de arquitectura orientada a servicios aborda precisamente aspectos relacionados con la supervisión y control de los servicios y sus interacciones.

La necesidad de gobernanza aparece porque un servicio compartido deja de ser una pieza puramente local. Un cambio que sería trivial en una aplicación aislada puede afectar a muchas otras cuando existe un contrato compartido. La arquitectura distribuida necesita mecanismos para conocer y controlar esas relaciones, especialmente cuando el número de servicios y consumidores crece.

El precio de distribuir una capacidad

Una llamada dentro del mismo proceso es muy diferente de una llamada que atraviesa una red. Pueden producirse problemas de latencia, errores de comunicación, indisponibilidad, cambios de versión o problemas de autenticación. Una aplicación que antes realizaba una operación local puede necesitar ahora gestionar todos estos escenarios.

Además, cuando un proceso depende de varios servicios, la disponibilidad global puede verse afectada por sus dependencias. Si una operación necesita consultar clientes, precios, inventario y pagos, debemos decidir qué ocurre cuando uno de ellos no responde, si podemos reintentar, si existe una alternativa o si la operación debe detenerse.

Por eso convertir una capacidad en servicio siempre implica asumir un coste de distribución. Ese coste puede estar justificado por la reutilización, la integración entre sistemas, la necesidad de evolución independiente o una frontera clara de responsabilidad, pero no debería introducirse simplemente porque separar el código parezca más elegante.

Cuándo tiene sentido una arquitectura orientada a servicios

La pregunta práctica puede formularse de una manera bastante sencilla: ¿existe una capacidad que tenga sentido ofrecer independientemente de una aplicación concreta? Si la respuesta es afirmativa, podemos analizar si existen consumidores adicionales, una necesidad de evolución independiente, sistemas heterogéneos que deban integrarse o una razón clara para ocultar la implementación.

Por el contrario, una funcionalidad pequeña, utilizada por un único consumidor y que siempre evoluciona junto con el resto de la aplicación puede beneficiarse más de permanecer integrada. No existe una obligación arquitectónica de convertirla en servicio solo porque técnicamente sea posible hacerlo.

La decisión puede resumirse en cuatro preguntas:

1. ¿Es una capacidad claramente identificable?
2. ¿Necesita ser utilizada o evolucionar
   independientemente?
3. ¿Podemos definir un contrato estable?
4. ¿El beneficio supera el coste de distribuirla?
              ┌──────┴──────┐
             Sí             No
              │              │
              ▼              ▼
       Considerar       Mantener integrado
       un servicio       o revisar el diseño

Este recorrido no pretende convertirse en una fórmula matemática. Sirve para separar tres decisiones que a menudo se mezclan: primero decidimos si existe una capacidad que merece una frontera propia; después definimos el contrato; finalmente elegimos cómo implementarla y desplegarla. Empezar por la tecnología —por ejemplo, decidir primero que queremos crear una API— puede llevarnos a diseñar servicios alrededor de detalles técnicos en lugar de alrededor de responsabilidades reales.

También debemos recordar que la decisión puede cambiar con el tiempo. Una capacidad puede comenzar como un módulo dentro de un monolito y convertirse posteriormente en un servicio cuando aparezcan nuevos consumidores o una necesidad real de independencia. Diseñar con fronteras de responsabilidad claras permite retrasar el coste de la distribución hasta que exista una razón para asumirlo.

Una matriz de decisión resumida

Podemos condensar el análisis anterior en una matriz que sirva como referencia rápida. No pretende sustituir al diseño arquitectónico, sino ayudarnos a identificar los factores que hacen más razonable una separación.

Criterio Pregunta Si la respuesta es «sí» Si la respuesta es «no»
Capacidad ¿Representa una responsabilidad clara del negocio? Candidato a servicio Mantener integrado o revisar la frontera
Reutilización ¿Existen varios consumidores actuales o previsibles? Favorece la creación del servicio No hay una necesidad clara de reutilización
Independencia ¿Necesita evolucionar o desplegarse de forma independiente? Favorece separar la capacidad Puede permanecer dentro de la aplicación
Encapsulación ¿Queremos ocultar datos y reglas internas? El servicio proporciona una frontera útil La separación puede aportar poco valor
Contrato ¿Podemos definir una interfaz estable y comprensible? Existe una base adecuada Refinar primero la responsabilidad
Integración ¿Debe ser consumido por sistemas heterogéneos? Favorece exponer una capacidad común La separación puede aportar poco valor
Escalabilidad ¿Tiene necesidades de escalado diferentes? Puede justificar una implementación independiente No existe una necesidad técnica evidente
Coste ¿El beneficio compensa la complejidad remota? La separación puede estar justificada Mantenerlo integrado puede ser preferible

La matriz permite distinguir entre señales que favorecen una frontera y señales que aconsejan mantener la funcionalidad integrada. No todos los criterios tienen el mismo peso en todos los sistemas, y algunos pueden ser suficientes por sí mismos en determinados contextos. Por ejemplo, una fuerte necesidad de integración con sistemas externos puede justificar una frontera aunque solo exista inicialmente un consumidor.

También ayuda a evitar un criterio poco útil: el tamaño del código. Un componente pequeño puede justificar un servicio si representa una capacidad compartida que necesita evolucionar independientemente, mientras que otro mucho mayor puede funcionar mejor dentro del mismo proceso si pertenece a una única responsabilidad. La cuestión no es cuánto código queremos separar, sino qué relación queremos establecer entre esa capacidad y el resto del sistema.

Una forma diferente de pensar la reutilización

La arquitectura orientada a servicios propone, en última instancia, una forma diferente de mirar el software. Una aplicación deja de ser necesariamente la unidad alrededor de la cual organizamos todas las capacidades y pasa a ser uno de los consumidores de un conjunto más amplio de servicios. Una capacidad como clientes, pagos, pedidos o envíos puede ser utilizada por diferentes aplicaciones siempre que exista un contrato que defina cómo interactuar con ella.

Los tres conceptos fundamentales se entienden entonces como partes de una misma idea. El contrato define qué ofrece un servicio; la implementación determina cómo consigue ofrecerlo; y la composición permite combinar varios servicios para construir capacidades y procesos más complejos. La reutilización aparece cuando estas piezas están suficientemente bien diseñadas para que diferentes consumidores puedan utilizarlas sin depender de sus detalles internos.

La arquitectura orientada a servicios tampoco consiste simplemente en dividir una aplicación en varios servidores o en crear una colección de APIs. Su aportación está en identificar capacidades que merecen una frontera propia, definir contratos que permitan consumirlas y organizar su colaboración de manera que diferentes aplicaciones puedan aprovecharlas.

En este sentido, podemos establecer una conexión con la arquitectura basada en eventos. En SOA, una pregunta habitual es «¿qué capacidad necesito y qué servicio puede proporcionármela?». En una arquitectura basada en eventos, la pregunta cambia hacia «¿qué ha ocurrido y quién necesita reaccionar?». Ambas pueden convivir: podemos utilizar servicios para solicitar capacidades de manera explícita y eventos para comunicar acontecimientos que otros componentes necesitan conocer.

Al final, una arquitectura orientada a servicios intenta conseguir algo más profundo que reutilizar código: convertir las capacidades del software en piezas explícitas, consumibles y combinables, de manera que construir una nueva aplicación no implique necesariamente volver a construir todo lo que ya sabe hacer la organización. Cuando los servicios tienen responsabilidades claras, los contratos son comprensibles y las implementaciones permanecen ocultas, el software deja de ser únicamente un conjunto de aplicaciones independientes y empieza a comportarse como un ecosistema de capacidades que pueden combinarse para crear nuevas soluciones.