En sus inicios, Circit operaba con un pequeño equipo de menos de 10 empleados, haciendo malabares con múltiples productos y enfrentándose a recursos y conocimientos limitados en el campo emergente de la banca abierta. Con la llegada de la PSD2 y el impulso del sector hacia la estandarización, surgió la oportunidad de revolucionar la forma en que los auditores accedían a los datos bancarios. Sin embargo, la complejidad de la tarea parecía desalentadora, sobre todo teniendo en cuenta la incipiente posición de Circit en el mercado.

Reconociendo la necesidad de adoptar un enfoque estructurado, se embarcó en un viaje familiar para muchos productos de software: definir un modelo de dominio, establecer terminologías ubicuas, crear un modelo canónicoy elaborar un conjunto de abstracciones. Estos pasos fundacionales proporcionaron una hoja de ruta para el desarrollo futuro, pero también pusieron de relieve el reto de tomar decisiones concretas con recursos y conocimientos limitados.

Para superar este obstáculo, Circit tomó la decisión estratégica de aprovechar los marcos existentes, centrándose especialmente en el mercado británico, donde la empresa tenía sus principales operaciones. Al alinearse con el marco de Open Banking del Reino Unido, Circit pudo acelerar su proceso de desarrollo y ofrecer soluciones adaptadas a las integraciones bancarias locales.

Sin embargo, a medida que la empresa se expandía a nuevos mercados, como Europa y más allá, se enfrentaba a una nueva serie de retos. Muchas de las solicitudes de integración procedían de bancos que no tenían su sede en el Reino Unido, lo que dejaba obsoletos el modelo de dominio y las terminologías existentes. Una vez más, Circit se encontró en una encrucijada y tuvo que revisar su enfoque para adaptarse a las distintas necesidades del mercado.

Al darnos cuenta de lo poco práctico que resultaba crear integraciones a medida para cada nuevo mercado, adoptamos una estrategia basada en los principios de dividir y operar. A medida que surgían nuevos marcos bancarios como XS2A, STET y EBICS, los transformábamos en lo que ellos denominaban "Protocolos", a la postre subdominios. Estos Protocolos encapsulaban las funcionalidades básicas de cada marco, proporcionando una interfaz normalizada para la integración.

Sin embargo, el reto residía en las diversas opciones de implementación que ofrecían los bancos dentro de cada marco. Por ejemplo, mientras que el protocolo STET ofrecía un conjunto estandarizado de características, los bancos podían aplicarlo con variaciones en los enfoques de autorización de clientes, como Basic, JWT o TLS. Reconocimos la necesidad de una solución flexible capaz de acomodar estas variaciones sin sacrificar la eficiencia o la escalabilidad.

Para hacer frente a este reto, hemos aprovechado el poder de la virtualización y la abstracción dentro del lenguaje C# del lenguaje de programación C#. Al abstraer los detalles específicos de la implementación y crear representaciones virtuales de los protocolos bancarios, desarrollamos una arquitectura modular y ampliable.

// Define an abstract base class for banking protocols
public abstract class BankingProtocol
{
    // Define common methods and properties for all protocols
    public abstract void HandleAuthorization(string authorizationMethod);
}

// Define concrete implementations for specific banking protocols
public class STETProtocol : BankingProtocol
{
    public override void HandleAuthorization(string authorizationMethod)
    {
        // Implement STET-specific authorization handling logic
        Console.WriteLine($"Handling authorization for STET protocol using {authorizationMethod} method.");
    }
}

public class XS2AProtocol : BankingProtocol
{
    public override void HandleAuthorization(string authorizationMethod)
    {
        // Implement XS2A-specific authorization handling logic
        Console.WriteLine($"Handling authorization for XS2A protocol using {authorizationMethod} method.");

  • Definimos una clase base abstracta BankingProtocol con un método HandleAuthorization, que representa el comportamiento común para todos los protocolos bancarios.
  • Las implementaciones concretas(STETProtocol y XS2AProtocol) heredan de la clase base y proporcionan la lógica específica del protocolo para gestionar la autorización.
  • Utilizamos una clase de fábrica ProtocolFactory para crear instancias de protocolos específicos basados en el nombre de protocolo proporcionado.
  • En el método Main, demostramos cómo utilizar la fábrica para crear una instancia de protocolo e invocar el método HandleAuthorization

.

Al adoptar un enfoque de diseño que priorizaba el aislamiento por proveedor de API, redujimos al mínimo la redundancia y agilizamos los esfuerzos de desarrollo. A pesar de enfrentarse a protocolos casi idénticos en varios proveedores, la empresa implantó un sistema que garantizaba que cada proveedor funcionara de forma independiente. Este enfoque, basado en los principios de abstracción procedimental y conceptual, nos permitió reducir significativamente la sobrecarga de desarrollo. En lugar de duplicar código para protocolos similares, aprovechamos la potencia de la inyección de dependencias y la configuración. De este modo, la empresa eliminó la necesidad de realizar tareas de codificación repetitivas, lo que permitió a los desarrolladores centrarse en configurar las dependencias en lugar de reescribir el código. Esta estrategia proactiva no sólo mitigó el riesgo de duplicación de código, sino que también mejoró el mantenimiento y la escalabilidad.

public abstract class ApiProvider
{
    public abstract void HandleRequest(string requestData);
}

// Define concrete implementations for API providers corresponding to different protocols
public class STETApiProvider : ApiProvider
{
    public override void HandleRequest(string requestData)
    {
        // Implement logic specific to handling requests for the STET protocol
        Console.WriteLine("Handling request using STET protocol: " + requestData);
    }
}

public class XS2AApiProvider : ApiProvider
{
    public override void HandleRequest(string requestData)
    {
        // Implement logic specific to handling requests for the XS2A protocol
        Console.WriteLine("Handling request using XS2A protocol: " + requestData);
    }
}

En este ejemplo de código:

  • Definimos una clase base abstracta ApiProvider que representa el comportamiento común de todos los proveedores de API.
  • Implementaciones concretas (STETApiProvider y XS2AApiProvider) heredan de la clase base y proporcionan la lógica específica del protocolo para gestionar las solicitudes.
  • Utilizamos una clase de fábrica ApiProviderFactory para crear instancias de proveedores de API basadas en el nombre de protocolo proporcionado.
  • En el método Main, demostramos cómo utilizar la fábrica para crear un proveedor de API basado en el nombre de protocolo especificado y simular la gestión de una solicitud utilizando el proveedor creado.

Una vez establecidas las abstracciones fundamentales de bajo nivel y a nivel de protocolo, podemos establecer abstracciones a nivel de dominio que encapsulen a cada proveedor de API.

using System;

// Define an abstract base class for API providers
public abstract class ApiProvider
{
    public abstract void HandleRequest(string requestData);
}

// Define concrete implementations for API providers corresponding to different protocols
public class STETApiProvider : ApiProvider
{
    public override void HandleRequest(string requestData)
    {
        // Implement logic specific to handling requests for the STET protocol
        Console.WriteLine("Handling request using STET protocol: " + requestData);
    }
}

public class XS2AApiProvider : ApiProvider
{
    public override void HandleRequest(string requestData)
    {
        // Implement logic specific to handling requests for the XS2A protocol
        Console.WriteLine("Handling request using XS2A protocol: " + requestData);
    }
}

En este ejemplo actualizado:

  • Introducimos una BankApi que representa una abstracción a nivel de dominio para un proveedor de API. Esta clase encapsula el proveedor de API específico del protocolo de bajo nivel subyacente.
  • El sitio BankApi toma un nombre de protocolo como parámetro durante la instanciación y crea internamente el correspondiente proveedor de API de bajo nivel utilizando ApiProviderFactory.
  • La dirección HandleRequest de la BankApi delega la gestión de las solicitudes al proveedor de la API subyacente.

La utilización de estas abstracciones a nivel de dominio nos permite implementar sin problemas integraciones adicionales sin interferir con los protocolos existentes, simplemente implementando nuevas abstracciones a nivel de dominio.

Ahora te presentamos el primer Patrón que utilizamos llamado Abstract Factory.

public static class ApiProviderFactory
{
    public static ApiProvider CreateApiProvider(string protocolName)
    {
        // Logic to determine which API provider to instantiate based on the protocol name
        switch (protocolName.ToUpper())
        {
            case "STET":
                return new STETApiProvider();
            case "XS2A":
                return new XS2AApiProvider();
            default:
                throw new ArgumentException("Unsupported protocol name.");
        }
    }
}

// Define a domain-level abstraction representing an API provider
public class BankApi
{
    private readonly ApiProvider _apiProvider;

    public BankApi(string protocolName)
    {
        // Create an API provider based on the specified protocol name
        _apiProvider = ApiProviderFactory.CreateApiProvider(protocolName);
    }

    // Method to handle a request using the underlying API provider
    public void HandleRequest(string requestData)
    {
        _apiProvider.HandleRequest(requestData);
    }
}

En este ejemplo:

  • Introducimos una BankApi que representa una abstracción a nivel de dominio para un proveedor de API. Esta clase encapsula el proveedor de API específico del protocolo de bajo nivel subyacente.
  • El sitio BankApi toma un nombre de protocolo como parámetro durante la instanciación y crea internamente el correspondiente proveedor de API de bajo nivel utilizando la clase ApiProviderFactory.
  • La dirección HandleRequest de la BankApi delega la gestión de las solicitudes al proveedor de la API subyacente.

Los procesos de integración constan de dos componentes fundamentales: La autorización y la recuperación de datos. Ambos elementos se abstraen de forma similar a lo descrito anteriormente, empezando por la autorización.

Al principio, nuestro enfoque se centraba en un flujo de autenticación redirigido. Sin embargo, a medida que nuestras operaciones se expandían, nos adentramos en una miríada de métodos alternativos, incluidos los enfoques Embedded y Decoupled. Además, una parte significativa de los bancos implementan múltiples flujos de autorización, lo que añade capas de complejidad al panorama. En consecuencia, cada proveedor de API y protocolo puede conceptualizarse como una colección de abstracciones. Esto nos lleva perfectamente al siguiente tema de debate: el patrón de estrategia.

// Define an abstract base class for Authorization strategies
public abstract class AuthorizationStrategy
{
    public abstract void Authorize();
}

// Concrete implementations for Authorization strategies
public class RedirectAuthorization : AuthorizationStrategy
{
    public override void Authorize()
    {
        Console.WriteLine("Performing redirect authorization...");
    }
}

public class EmbeddedAuthorization : AuthorizationStrategy
{
    public override void Authorize()
    {
        Console.WriteLine("Performing embedded authorization...");
    }
}

// Factory class to create instances of Authorization strategies based on the approach
public static class AuthorizationStrategyFactory
{
    public static AuthorizationStrategy CreateAuthorizationStrategy(string approach)
    {
        switch (approach.ToUpper())
        {
            case "REDIRECT":
                return new RedirectAuthorization();
            case "EMBEDDED":
                return new EmbeddedAuthorization();
            // Add more cases for other authorization approaches as needed
            default:
                throw new ArgumentException("Unsupported authorization approach.");
        }
    }
}

// Domain-level abstraction representing authorization for a specific API provider
public class AuthorizationProcess
{
    private readonly AuthorizationStrategy _authorizationStrategy;

    public AuthorizationProcess(string approach)
    {
        _authorizationStrategy = AuthorizationStrategyFactory.CreateAuthorizationStrategy(approach);
    }

    public void PerformAuthorization()
    {
        _authorizationStrategy.Authorize();
    }
}

En este ejemplo:

  • Introducimos un nuevo conjunto de abstracciones para los procesos de autorización, siguiendo la misma estructura que los proveedores de API.
  • El sitio AuthorizationStrategyFactory crea instancias de estrategias de autorización concretas basadas en el enfoque proporcionado.
  • El sitio AuthorizationProcess encapsula la estrategia de autorización seleccionada y proporciona un método para realizar el proceso de autorización.
  • El método Main demuestra cómo utilizar el proceso de autorización, de forma similar a como utilizamos el proveedor de API en el ejemplo anterior.

Con un giro estratégico, nos embarcamos en una búsqueda para encapsular la miríada de enfoques de autorización que tenían ante sí. Desde el conocido flujo de redirección hasta las complejidades de la autorización integrada y desacoplada, cada método encontró su representación en la arquitectura en evolución de Circit.

La base de esta transformación fue el establecimiento de una abstracción fundamental: AuthorizationStrategy. Esta entidad abstracta sirvió como modelo para los innumerables enfoques de autorización que encontraríamos. Implementaciones concretas como RedirectAuthorization y EmbeddedAuthorization dieron vida a estas abstracciones, cada una representando una faceta única del panorama de la autorización.

Pero la abstracción por sí sola no era suficiente. En Circit necesitábamos un mecanismo para instanciar estas estrategias sin problemas, adaptándose a los requisitos matizados de cada integración. Así nació AuthorizationStrategyFactory. Con esta fábrica a su disposición, podíamos conjurar la estrategia de autorización precisa necesaria para cualquier escenario dado, ya fuera la simplicidad de una redirección o la sofisticación de un flujo integrado.

A medida que se asentaba el polvo y el sistema de Circit maduraba, el AuthorizationProcess surgió como el eje de su marco de autorización. Esta abstracción a nivel de dominio sirvió como conducto entre la estrategia y la ejecución, orquestando las complejidades de la autorización con delicadeza y precisión.

Armados con el patrón de estrategia y reforzados por abstracciones a nivel de dominio, estábamos preparados para afrontar el cambiante panorama de la integración bancaria. Con cada nuevo reto, adoptaron la versatilidad y adaptabilidad que les ofrecía su diseño estratégico, avanzando hacia un futuro lleno de posibilidades e innovación.

En cuanto al proceso de recuperación de datos, nos centramos en tres operaciones principales: recuperar todas las cuentas, recuperar los saldos de cada cuenta y obtener los detalles de las transacciones. Esto resume la complejidad de nuestro mecanismo de recuperación de datos.

Para agilizar este proceso, empleamos el patrón Template Method. Esta elección estratégica nos permite construir un canal cohesivo utilizando abstracciones, lo que facilita la recuperación de datos sin fisuras.

// Abstract class representing the data retrieval process
public abstract class DataRetrievalProcess
{
    // Template method defining the data retrieval pipeline
    public void RetrieveData()
    {
        // Step 1: Fetch all accounts
        List<Account> accounts = FetchAllAccounts();

        // Step 2: Retrieve balances for each account
        foreach (var account in accounts)
        {
            decimal balance = RetrieveBalance(account);
            Console.WriteLine($"Balance for Account {account.AccountNumber}: {balance}");
        }

        // Step 3: Retrieve transaction details for each account
        foreach (var account in accounts)
        {
            List<Transaction> transactions = RetrieveTransactions(account);
            Console.WriteLine($"Transactions for Account {account.AccountNumber}:");
            foreach (var transaction in transactions)
            {
                Console.WriteLine(transaction);
            }
        }
    }

    // Abstract methods representing steps in the data retrieval process
    protected abstract List<Account> FetchAllAccounts();
    protected abstract decimal RetrieveBalance(Account account);
    protected abstract List<Transaction> RetrieveTransactions(Account account);
}

// Sample Account class
public class Account
{
    public string AccountNumber { get; set; }
    // Other properties
}

// Sample Transaction class
public class Transaction
{
    // Transaction properties
}

// Concrete implementation of the data retrieval process
public class ConcreteDataRetrievalProcess : DataRetrievalProcess
{
    // Mock data for demonstration purposes
    protected override List<Account> FetchAllAccounts()
    {
        return new List<Account>
        {
            new Account { AccountNumber = "123456789" },
            new Account { AccountNumber = "987654321" }
        };
    }

    protected override decimal RetrieveBalance(Account account)
    {
        // Mock balance retrieval logic
        return 1000.00m; // Dummy balance value
    }

    protected override List<Transaction> RetrieveTransactions(Account account)
    {
        // Mock transaction retrieval logic
        return new List<Transaction>
        {
            new Transaction { /* Transaction details */ },
            new Transaction { /* Transaction details */ }
        };
    }
}

En este ejemplo de código:

  • Hemos establecido una clase abstracta Proceso de recuperación de datos para encapsular el flujo de trabajo de recuperación de datos. Proporciona un método de plantilla RecuperarDatos() que define la secuencia que abarca la obtención de cuentas, saldos y transacciones.
  • Las subclases concretas ofrecen implementaciones para FetchAllAccounts(), RecuperarSaldo()y RecuperarTransacciones()adaptando el comportamiento a las distintas fuentes de datos.
  • Una implementación concreta, ConcreteDataRetrievalProcessproporciona una lógica de recuperación de datos simulada, mostrando la aplicación práctica del patrón.
  • En el método Main, instanciamos ConcreteDataRetrievalProcess y ejecutamos el proceso de recuperación de datos.

Como hemos comentado anteriormente, la gestión de cientos de integraciones implica dar cabida a diversos contratos e interpretaciones de datos en varias plataformas. Cada integración aporta sus propios matices, como las diferentes interpretaciones de los tipos de saldo. Sin embargo, en medio de esta complejidad, reconocimos la oportunidad de aprovechar un modelo de datos canónico, proporcionando una representación unificada de los puntos de datos esenciales.

Este modelo de datos canónico nos sirve de base y ofrece un formato estandarizado para gestionar los datos en todas las integraciones. Nos permite identificar y priorizar los datos valiosos en función de las necesidades del cliente, garantizando la consistencia y coherencia de nuestras operaciones.

Para salvar la distancia entre el modelo de datos canónico y los requisitos exclusivos de cada proveedor de API, hemos introducido un nuevo patrón: el patrón Adaptador. En el nivel del proveedor de API, los adaptadores son responsables de traducir los datos del modelo canónico al formato específico requerido por cada integración. Esta delegación de responsabilidades garantiza una unificación perfecta de los datos al tiempo que se adapta a la idiosincrasia de cada plataforma.

// Canonical data model representing essential data points
public class CanonicalDataModel
{
    public string AccountNumber { get; set; }
    public decimal Balance { get; set; }
    // Other properties relevant to the canonical model
}

// Interface for API provider adapters
public interface IApiProviderAdapter
{
    void ConvertAndSendData(CanonicalDataModel data);
}

// Concrete adapter for API provider A
public class ApiProviderAAdapter : IApiProviderAdapter
{
    public void ConvertAndSendData(CanonicalDataModel data)
    {
        // Convert canonical data to format specific to API provider A
        Console.WriteLine("Converting data to format specific to API provider A:");
        Console.WriteLine($"Account: {data.AccountNumber}, Balance: {data.Balance}");
        // Send data to API provider A
        Console.WriteLine("Sending data to API provider A...");
    }
}

// Concrete adapter for API provider B
public class ApiProviderBAdapter : IApiProviderAdapter
{
    public void ConvertAndSendData(CanonicalDataModel data)
    {
        // Convert canonical data to format specific to API provider B
        Console.WriteLine("Converting data to format specific to API provider B:");
        Console.WriteLine($"Account: {data.AccountNumber}, Balance: {data.Balance}");
        // Send data to API provider B
        Console.WriteLine("Sending data to API provider B...");
    }
}

// Client class responsible for orchestrating data unification and sending to API providers
public class DataUnificationClient
{
    private readonly List<IApiProviderAdapter> _adapters;

    public DataUnificationClient()
    {
        // Initialize adapters for different API providers
        _adapters = new List<IApiProviderAdapter>
        {
            new ApiProviderAAdapter(),
            new ApiProviderBAdapter()
            // Add more adapters for other API providers as needed
        };
    }

    // Method to unify data and send to all API providers
    public void UnifyAndSendData(CanonicalDataModel data)
    {
        foreach (var adapter in _adapters)
        {
            adapter.ConvertAndSendData(data);
        }
    }
}

En este ejemplo:

  • Definimos un modelo de datos canónico (CanonicalDataModel) que representa puntos de datos esenciales compartidos por distintos proveedores de API.
  • Creamos una interfaz IApiProviderAdapter que representa el patrón del adaptador, con un método ConvertAndSendData para convertir los datos canónicos y enviarlos al proveedor de API correspondiente.
  • Clases concretas de adaptadores (ApiProviderAAdapter y ApiProviderBAdapter) implementan la clase IApiProviderAdapter para convertir datos canónicos a formatos específicos de los proveedores de API A y B, respectivamente.
  • El DataUnificationClient organiza el proceso de unificación de datos iterando a través de una lista de adaptadores y enviando los datos convertidos a todos los proveedores de API.
  • En el método Main, creamos una instancia del modelo de datos canónico, instanciamos el método DataUnificationClienty unificamos los datos, demostrando el patrón adaptador en acción para la unificación de datos a través de múltiples proveedores de API.

En conclusión, el viaje de Circit subraya el poder transformador de la adopción de patrones de diseño estratégicos para abordar las complejidades de la integración de API. Partiendo de unos recursos y una experiencia limitados, Circit aprovechó cambios normativos como la PSD2 para redefinir el acceso de los auditores a los datos. Al adoptar patrones como el de estrategia, la empresa navegó con agilidad por la expansión del mercado y la evolución de las demandas de integración.

La adopción de un modelo de datos canónico facilitó la representación fluida de los datos en todas las integraciones, mientras que los adaptadores salvaron las distancias entre el modelo canónico y los requisitos específicos de las API. Además, el arsenal de integración de Circit se reforzó con patrones adicionales como mecanismos de reintento y procesamiento en paralelo, garantizando la fiabilidad y la optimización del rendimiento.

En esencia, la historia de éxito de Circit ejemplifica cómo la adopción estratégica de patrones de diseño permite a las organizaciones adaptarse a los cambios normativos, satisfacer las cambiantes demandas del mercado y desbloquear nuevos niveles de eficiencia e innovación en la integración de API.

Al concluir nuestro debate sobre las innovadoras soluciones de Circit para la integración de API y la gestión de datos, le invitamos a explorar algunas preguntas intrigantes:

  1. API de consentimiento en detalle: ¿Cómo garantiza la API de consentimiento de Circit la privacidad del usuario al tiempo que permite un acceso sin fisuras a datos críticos con fines de auditoría?
  2. El artículo 10 en detalle: ¿Qué aporta el artículo 10 a la documentación de Circit y cómo contribuye a nuestra comprensión del cumplimiento de la normativa y los protocolos de tratamiento de datos?
  3. MATLS.
  4. Flujos de autenticación desmitificados: ¿Le gustaría profundizar en los flujos de autenticación de Circit, como la redirección, la desvinculación y la autenticación integrada? Aprenda cómo funciona cada flujo y sus implicaciones para la seguridad y la experiencia del usuario.
  5. Registro dinámico de clientes: ¿Siente curiosidad por el registro dinámico de clientes y su función en OAuth 2.0? Explore su importancia en el ecosistema de Circit y cómo facilita una integración perfecta con varios proveedores de API.

Nos complace profundizar en estos temas en futuras publicaciones, para ofrecerle un conocimiento exhaustivo de la pila tecnológica y las estrategias normativas de Circit. Permanezca atento para conocer más detalles y descubrimientos.

Descargar pdf
Solicite una demostración

Vea lo que Circit puede hacer por su empresa