Qué es la lógica de negocio en programación y cómo distinguirla de la lógica de aplicación y de pantalla
Foto de Anastasiya Romanova en Unsplash
A menudo me he encontrado con que en el mundo del desarrollo de software, a cualquier cosa se le llama lógica de negocio o lógica a secas.
En este artículo voy a explicarte qué entiendo por lógica y qué tres tipos básicos podemos distinguir.
Conocer la diferencia entre ellos genera un marco conceptual que facilita la comunicación entre los miembros del equipo y además ayuda a la creación de una buena arquitectura.
1. ¿Qué es la lógica en programación?
Imagina que el código que escribimos es como un camino. Lo deseable es que ese camino sea recto y no haya bifurcaciones. Cuanto más recto sea el camino, más fácil será seguirlo y en consecuencia, entenderlo.
Cuando el camino se bifurca nos obliga a usar memoria y comprensión. "Si pasa esto voy por aquí", pero "si pasa esto otro voy por allá". Al escoger una bifurcación, memorizamos el lugar por el que nos hemos desviado para, más adelante, volver e investigar qué hay por el otro camino.
Estas desviaciones las implementamos con cláusulas if's.
La lógica en programación es el conjunto de todos estos if's, o decisiones que debe tomar nuestro código en función de los inputs recibidos.
Pero no toda la lógica que escribimos tiene las mismas características. Es importante distinguir entre:
- Lógica de negocio
- Lógica de aplicación
- Lógica de pantalla
2. Lógica de negocio
A grandes rasgos, una aplicación de software trata de representar una situación que se está dando en el mundo real.
Por ejemplo, en lugar de ir al almacén y contabilizar cuánto stock queda de un producto, puedo ir a mi aplicación y consultarlo.
La lógica de negocio es aquella que toma decisiones en función de conceptos reales y que utiliza el vocabulario que un experto en la materia utilizaría en su día a día.
Cada aplicación tendrá una lógica de negocio basada en el modelo que representa. En un CRM, las decisiones que se tomen (bloques if's) estarán relacionadas, por ejemplo, con si un contacto pasa de prospecto a cliente, o si se debe realizar una acción futura con ese cliente.
Una aplicación para almacenes tomará decisiones en función de la recepción/envío de productos.
Este tipo de lógica es la más importante y también la más complicada.
Se suele colocar en la capa más interna de la aplicación, la denominada capa de dominio, y debería estar aislada de cualquier referencia externa como la base de datos, el sistema de archivos o una api de terceros.
Veamos algún ejemplo.
Pongamos que queremos pagar una factura. La lógica de negocio hará lo siguiente:
- Dada una factura, validará que se cumplan ciertas condiciones, como por ejemplo que no esté ya pagada, y le cambiará su estado a "pagada". Su misión no es contactar con la api del banco y realizar la transferencia. Su misión es, por un lado, representar que mi modelo abstracto de factura está pagado, y por otro, que se cumplan una pre-condiciones antes de realizar el cambio y unas post-condiciones después del cambio.
Con el stock de un almacén quizá sea más claro.
- Si envío un producto a un cliente, resto stock del producto, pero mi aplicación no está enviando el producto en el camión. La aplicación comprobará si hay stock suficiente antes de enviar el producto.
La lógica de negocio son todas estas decisiones, a nivel abstracto, que están pasando en el mundo real.
Ejemplos de lógica de negocio:
- Calcular el total de una factura incluyendo los impuestos
- Inscribir a un alumno en una clase
- Planificar un proyecto
- Cerrar una incidencia
- Calcular los gastos de envío
- Ensilar café en un silo
- Reservar una habitación
3. Lógica de aplicación, También conocida como lógica de servicios
Su misión principal es preparar el contexto necesario y coordinar las decisiones que toma la lógica de negocio. Es una lógica de tipo técnico.
Por ejemplo, imagina que usamos una base de datos para persistir el estado de la aplicación. Cualquier decisión que tome nuestra aplicación para guardar o recuperar datos de la base de datos será puramente técnica, no estará relacionada con el modelo conceptual de negocio.
Las decisiones de lógica de aplicación suelen ser sencillas, y el camino que sigue el código ha de ser casi recto, sin bifurcaciones. Suele haber un único camino "feliz" y las bifurcaciones sólo se usan para salir del él porque alguna premisa no se cumple.
Además, la mayoría de bloques con este tipo de lógica tienen la siguiente estructura:
- Llamada externa a la aplicación. Por ejemplo, recuperar registros de la base de datos.
- Llamada interna a nuestra capa de negocio. Cambia el estado del sistema.
- Otra llamada externa. Por ejemplo, guardar los cambios que hemos realizado.
En programación funcional, a estos bloques de código se les llama "Impureim sandwich". Cuando lo explico, me tomo la libertad de llamarlos "métodos bocadillo". El concepto "impure/pure/impure sanwich" se refiere a conceptos funcionales teniendo en cuenta que las funciones se pueden dividir entre puras e impuras. Mark Seemann lo explica en perfectamente en algunos de sus posts. Te lo recomiendo.
Durante años se ha explicado que la lógica de negocio va en los servicios. Esto se basa en el modelo de capas en que el proyecto cliente depende del proyecto servicio y a su vez este depende de un proyecto de datos. Todo queda acoplado a los datos y además estos representan un modelo anémico.
El código resultante de esta arquitectura presenta numerosas limitaciones desde el punto de vista de la mantenibilidad, pero esto es otra historia y aquí no puedo entrar más en detalle por no alargarme.
Lo que sí puedo asegurar es que aplicar la lógica de aplicación usando métodos bocadillo genera código mantenible y fácil de modificar.
Veamos un ejemplo método de la capa Aplicación o Servicios:
public async Task<Result> PagarFacura(int facturaId)
{
var factura = await _database.Facturas.FindAsync(facturaId); // Bocadillo - > parte superior
if (factura is null) // lógica de aplicación
{
return Result.Failure($"Factura con id {facturaId} no encontrada");
}
var pago = factura.Pagar(); // llamada a la capa de negocio. Dentro se aplica lógica de negocio
if(pago.IsFailure) // lógica de aplicación para salir del camino principal
{
return Result.Failure(pago.Error);
}
await _database.SaveChangesAsync(); // Bocadillo - > parte inferior
return Result.Success();
}
El camino que toma el código es prácticamente recto. Las bifurcaciones solo controlan casos no deseados, como que no se encuentre la factura en la base de datos o que el pago no se realice por el motivo que sea.
Podríamos añadir más lógica de aplicación, como por ejemplo comprobar si el usuario tiene los permisos necesarios para realizar la acción, o también podríamos llamar a una api del banco para realizar la transferencia realmente.
Ejemplos de lógica de aplicación:
- Recuperar una factura de base de datos y comprobar si existe
- Guardar datos en base de datos
- Guardar un fichero en una carpeta
- Verificar si el usuario tiene permiso para realizar la acción
- Guardar la traza de la acción realizada por el usuario
4. Lógica de pantalla
Es aquella que toma decisiones sobre los elementos que se muestran en la pantalla y las interacciones que realiza el usuario. Por ejemplo, decide si un botón aparece o no aparece en la pantalla.
Imagina que en función del estado de la factura y los permisos del usuario se debe mostrar el botón de pagar. En cualquier otro caso el botón no debe aparecer en la pantalla.
<!-- un archivo .cshtml cualquiera -->
@if (Model.Factura.Estado == EstadoFactura.Pendiente && User.TienePermiso(Permiso.PagarFacturas))
{
<button class="btn btn-primary">Pagar factura</button>
}
La lógica de la pantalla puede llegar a ser muy complicada. Para estos casos conviene generar un modelo único para la pantalla y encapsular las decisiones en propiedades del modelo:
<!-- un archivo .cshtml cualquiera -->
@if (Model.MostrarBotonPagar)
{
<button class="btn btn-primary">Pagar factura</button>
}
Esto es posible si creamos la propiedad MostrarBotonPagar y la inicializamos de la siguiente manera:
// el archivo .cshtml.cs correspondiente
MostrarBotonPagar = Model.Factura.Estado == EstadoFactura.Pendiente && User.TienePermiso(Permiso.PagarFacturas);
De un modo u otro, la lógica de pantalla es la que controla qué elementos pueden aparecer en ella, así como las interacciones que realiza el usuario.
5. Conclusiones
Saber con qué tipo de lógica estamos tratando nos ayuda a saber dónde colocar bloques de código y a generar una arquitectura mantenible.
La comunicación es uno de los costes más importantes en el desarrollo de software y a menudo no se tiene en cuenta. Establecer estas bases conceptuales me ayuda a comunicarme mejor con el equipo y a evitar interpretaciones personales.