0

Estructuras de Datos en C# 12: Uso de estructuras de datos en Arquitectura Hexagonal

Introducción a la clase

En esta clase exploraremos cómo integrar estructuras de datos en Arquitectura Hexagonal (Ports & Adapters). Este enfoque permite crear aplicaciones flexibles y desacopladas, asegurando que la lógica de negocio sea independiente de la infraestructura y la interfaz de usuario.

¿Qué obtendrás de esta clase?

  • Comprender los principios de Arquitectura Hexagonal.
  • Aprender a utilizar estructuras de datos en la capa de aplicación y dominio.
  • Implementar un caso de uso real con listas, colas y diccionarios.
  • Crear un sistema desacoplado mediante puertos e implementaciones de adaptadores.

Arquitectura Hexagonal: Resumen rápido

Este patrón se basa en tres componentes clave:

  1. Núcleo de Aplicación (Core): Contiene la lógica de negocio.
  2. Puertos (Ports): Interfaces que definen las reglas de entrada y salida.
  3. Adaptadores (Adapters): Implementaciones concretas que interactúan con bases de datos, APIs o interfaces gráficas.

Este diseño permite cambiar la base de datos o la interfaz sin afectar la lógica de negocio.

Ejemplo práctico: Sistema de Gestión de Pedidos con Arquitectura Hexagonal

1. Definiendo el Dominio (Entidades y Reglas de Negocio)

Las entidades representan modelos de negocio, usando estructuras de datos como listas y diccionarios.

public class Pedido
{
    public int Id { get; }
    public string Cliente { get; }
    public decimal Total { get; }
    public EstadoPedido Estado { get; private set; }

    public Pedido(int id, string cliente, decimal total)
    {
        Id = id;
        Cliente = cliente;
        Total = total;
        Estado = EstadoPedido.Pendiente;
    }

    public void MarcarComoEnviado()
    {
        if (Estado == EstadoPedido.Pendiente)
            Estado = EstadoPedido.Enviado;
    }
}

public enum EstadoPedido
{
    Pendiente,
    Enviado,
    Entregado
}

Aquí definimos un objeto de dominio que usa un estado enumerado para gestionar el estado del pedido.

2. Definiendo los Puertos (Interfaces de Repositorio)

Los puertos definen interfaces que permiten desacoplar la lógica del almacenamiento.

public interface IPedidoRepositorio
{
    void Guardar(Pedido pedido);
    Pedido ObtenerPorId(int id);
    List<Pedido> ObtenerTodos();
}

Este puerto define operaciones comunes en la capa de persistencia.

3. Implementando los Adaptadores (Infraestructura de Datos con Listas y Diccionarios)

Aquí utilizamos estructuras de datos eficientes, como diccionarios y listas, para almacenar y buscar pedidos rápidamente.

using System.Collections.Generic;
using System.Linq;

public class PedidoRepositorioEnMemoria : IPedidoRepositorio
{
    private readonly Dictionary<int, Pedido> _pedidos = new Dictionary<int, Pedido>();

    public void Guardar(Pedido pedido)
    {
        _pedidos[pedido.Id] = pedido;
    }

    public Pedido ObtenerPorId(int id)
    {
        return _pedidos.ContainsKey(id) ? _pedidos[id] : null;
    }

    public List<Pedido> ObtenerTodos()
    {
        return _pedidos.Values.ToList();
    }
}

Este adaptador almacena pedidos en memoria, simulando una base de datos.

4. Creando un Caso de Uso: Procesamiento de Pedidos

Los casos de uso implementan la lógica de negocio y manipulan los datos sin depender de la infraestructura.

public class ProcesarPedidoUseCase
{
    private readonly IPedidoRepositorio _repositorio;

    public ProcesarPedidoUseCase(IPedidoRepositorio repositorio)
    {
        _repositorio = repositorio;
    }

    public void AgregarPedido(Pedido pedido)
    {
        _repositorio.Guardar(pedido);
    }

    public void EnviarPedido(int id)
    {
        Pedido pedido = _repositorio.ObtenerPorId(id);
        if (pedido != null)
        {
            pedido.MarcarComoEnviado();
            _repositorio.Guardar(pedido);
        }
    }
}

Aquí desacoplamos la lógica de negocio del almacenamiento, lo que permite cambiar la persistencia sin modificar el código del caso de uso.

5. Implementando la Interfaz de Usuario (CLI / API)

Este código simula una interacción con el usuario, utilizando una lista de pedidos y mostrando su estado.

using System;

class Program
{
    static void Main()
    {
        IPedidoRepositorio repositorio = new PedidoRepositorioEnMemoria();
        ProcesarPedidoUseCase procesarPedido = new ProcesarPedidoUseCase(repositorio);

        procesarPedido.AgregarPedido(new Pedido(1, "Carlos", 150.99m));
        procesarPedido.AgregarPedido(new Pedido(2, "Ana", 320.50m));

        Console.WriteLine("Ingrese el ID del pedido a enviar:");
        int id = Convert.ToInt32(Console.ReadLine());

        procesarPedido.EnviarPedido(id);
        Console.WriteLine($"Estado del pedido {id}: {repositorio.ObtenerPorId(id).Estado}");
    }
}

Este código permite al usuario registrar y actualizar pedidos, usando un diccionario para acceder rápidamente a los datos.

Caso de uso real

Un sistema de gestión de colas en bancos o supermercados puede usar una estructura de datos basada en colas para organizar a los clientes de manera eficiente.

Ejemplo en C#: Manejo de turnos con Cola

using System;
using System.Collections.Generic;

class TurnosBanco
{
    static void Main()
    {
        Queue<string> colaClientes = new Queue<string>();

        colaClientes.Enqueue("Cliente 1");
        colaClientes.Enqueue("Cliente 2");
        colaClientes.Enqueue("Cliente 3");

        while (colaClientes.Count > 0)
        {
            Console.WriteLine($"Atendiendo a: {colaClientes.Dequeue()}");
        }
    }
}

Aquí usamos una estructura FIFO (First In, First Out) para procesar clientes en orden de llegada.

Beneficios de integrar estructuras de datos en Arquitectura Hexagonal

BeneficioExplicación
ModularidadPermite cambiar la infraestructura sin afectar la lógica de negocio.
EscalabilidadSe pueden usar diferentes estructuras de datos según la necesidad.
DesacoplamientoSepara la lógica de negocio del almacenamiento y la interfaz.
MantenimientoCódigo limpio y estructurado en capas.

Desafío práctico

Ejercicio:

  1. Implementa un sistema de reservas donde los turnos se almacenen en una lista ordenada por fecha.
  2. Crea un sistema de notificaciones donde los mensajes se almacenen en una cola de prioridad y se procesen en orden de urgencia.

Resumen de la clase

  • Arquitectura Hexagonal separa la lógica de negocio en puertos y adaptadores.
  • Diccionarios, listas y colas permiten manipular datos de manera eficiente.
  • Casos de uso reales incluyen gestión de pedidos y turnos en bancos.

Próxima clase: Implementando algoritmos en Vertical Slice Architecture

En la siguiente clase exploraremos cómo aplicar algoritmos y estructuras de datos dentro de Vertical Slice Architecture, permitiendo desarrollar funcionalidades aisladas y altamente cohesivas.

Fernando Sonego

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *