Disyuntiva lógica: negocio vs. aplicación
⏱ Dedicación recomendada: 0 minutos
Esto considera el contenido visible y relevante, e ignora texto colapsado o marcado como opcional.
Hasta ahora, todo nuestro trabajo se ha realizado en el módulo app
de la aplicación. Sin embargo, no nos hemos detenido a analizar si todo el código debería estar realmente en ese módulo. Esto nos lleva a la pregunta clave: ¿qué criterios debemos seguir para decidir qué código va en qué módulo?
En términos sencillos, buscamos una clara separación de responsabilidades. Es decir, queremos diferenciar entre lo que es necesario para que la aplicación funcione y lo que es necesario para que la aplicación cumpla con su propósito específico. Esta distinción nos lleva a separar la lógica de negocio de la lógica de aplicación
Lógica de Negocios
La lógica de negocios se refiere a las reglas y procesos específicos del dominio o problema que tu aplicación está resolviendo. Esta lógica es independiente de la forma en que los datos entran o salen de la aplicación, es decir, no está vinculada a cómo la aplicación interactúa con bases de datos, interfaces de usuario, servicios web, etc. La lógica de negocio suele manejar:
- Validaciones de datos específicos del dominio.
- Reglas de cálculo.
- Políticas, procesos, y toma de decisiones que resuelven un problema dentro del dominio.
- Agregar, modificar o consultar objetos de dominio (entidades, agregados, etc.).
Ejemplo: Si estás creando una aplicación de gestión de usuarios, la lógica de negocios incluiría las reglas para validar un nombre de usuario, calcular permisos, o verificar la integridad de los datos de un usuario.
class UserService {
fun registerUser(user: User): Boolean {
// Aquí se definen reglas de validación específicas del dominio
if (user.name.isEmpty()) {
throw InvalidUserException("El nombre de usuario no puede estar vacío.")
}
return true // o más procesamiento basado en reglas de negocio
}
}
Lógica de Aplicación
La lógica de aplicación, por otro lado, es el pegamento que conecta tu aplicación con el mundo exterior (bases de datos, APIs, interfaces de usuario). Esta capa gestiona la interacción con la infraestructura y asegura que la lógica de negocio se ejecute en el contexto correcto. La lógica de aplicación se encarga de:
- Cargar y persistir datos en una base de datos o API.
- Manejar la seguridad y autorización de usuarios.
- Coordinar flujos de trabajo.
- Integrar interfaces de usuario o interfaces REST.
Ejemplo: En el mismo caso de gestión de usuarios, la lógica de aplicación sería el código que gestiona la comunicación con la base de datos o interfaz REST para persistir o cargar usuarios.
class UserController(val userService: UserService, val userRepository: UserRepository) {
fun handleUserRegistration(userData: UserDTO) {
// Conversión de DTO a entidad de dominio
val user = User(userData.name, userData.email)
// Aquí se llama a la lógica de negocios
if (userService.registerUser(user)) {
// Persistir el usuario en la base de datos o llamar a un API
userRepository.save(user)
}
}
}
Diferenciación clara
Para enseñar esta separación:
- Lógica de Negocios: Se debe extraer en una biblioteca separada, enfocándose solo en las reglas de negocio y sin acoplarse a frameworks específicos o servicios externos.
- Lógica de Aplicación: Se queda en la aplicación como tal, gestionando la infraestructura (base de datos, red, etc.).
Ventajas de la separación:
- Reutilización: Al tener la lógica de negocio en una biblioteca separada, puede ser reutilizada en diferentes proyectos.
- Mantenibilidad: Es más fácil mantener y actualizar reglas de negocio sin afectar el comportamiento general de la aplicación.
- Pruebas: Las pruebas unitarias para la lógica de negocio son más fáciles de realizar, ya que no están atadas a la infraestructura.
Separando la lógica en nuestra aplicación
Apliquemos los conceptos de lógica de negocio y lógica de aplicación en nuestro proyecto. Vamos a extraer la lógica de negocio de nuestra aplicación y moverla a la biblioteca lib
. Veamos primero la implementación actual de nuestra aplicación:
package cl.ravenhill
import kotlinx.datetime.Clock
fun echo(message: String) = "${Clock.System.now()} - $message"
fun main(args: Array<String>) {
for (arg in args) {
println(echo(arg))
}
}
En este código, la función main
representa la lógica de aplicación, ya que maneja la entrada y salida de la aplicación. Por otro lado, la función echo
constituye la lógica de negocio, encargada de procesar los mensajes recibidos sin depender de la infraestructura. Este diseño permite que ambas funciones se separen en módulos diferentes.
Agregando las dependencias de la biblioteca
Para empezar, debemos agregar la dependencia de kotlinx-datetime
a nuestra biblioteca. Abrimos el archivo build.gradle.kts
de la biblioteca y añadimos la dependencia correspondiente:
- Kotlin DSL
- Groovy DSL
dependencies {
implementation(libs.kotlinx.datetime)
}
dependencies {
implementation(libs.kotlinx.datetime)
}
Con esto, podemos eliminar la dependencia de kotlinx-datetime
del módulo app
, ya que la biblioteca lib
será la encargada de proveer esta funcionalidad.
Implementando la lógica de negocio
Primero, creamos un nuevo paquete en la biblioteca llamado cl.ravenhill
(o com.github.tu-usuario
si prefieres seguir el formato de tu usuario de GitHub). Dentro de este paquete, añadimos un archivo llamado Echo.kt
, que contendrá la lógica de negocio que hemos identificado.
Este archivo será el lugar donde se procesa la lógica independiente de la infraestructura, es decir, donde no hay gestión de entradas o salidas específicas de la aplicación:
package com.github.tu_usuario.echo
import kotlinx.datetime.Clock
fun echo(message: String) = "${Clock.System.now()} - $message"
Esta implementación permite reutilizar la función echo
desde distintos módulos o aplicaciones, sin que dependa de la lógica específica de la aplicación.
Por último, removemos la función echo
del módulo app
y actualizamos la función main
para que utilice la función echo
de la biblioteca lib
:
package com.github.tu_usuario.echo
fun main(args: Array<String>) {
for (arg in args) {
println(echo(arg))
}
}
- Enunciado
- Solución
Estás construyendo una aplicación simple de gestión de tareas. Actualmente, el código está todo en un solo archivo, lo que lo hace más difícil de mantener y reutilizar. Tu tarea es identificar las partes que corresponden a la lógica de negocio y las que corresponden a la lógica de aplicación.
Código Inicial
class Item(
val id: String,
val name: String,
val quantity: Int,
val lastUpdated: String
) {
fun copy(
id: String = this.id,
name: String = this.name,
quantity: Int = this.quantity,
lastUpdated: String = this.lastUpdated
) = Item(id, name, quantity, lastUpdated)
}
val items = mutableListOf<Item>()
fun addItem(name: String, quantity: Int) {
val id = UUID.randomUUID().toString() // Generar un ID único
val currentTime = Clock.System.now()
.toLocalDateTime(TimeZone.currentSystemDefault()).toString()
val item = Item(id, name, quantity, currentTime)
items.add(item)
println("Item added: $name with quantity $quantity")
}
fun updateItem(id: Int, quantity: Int) {
val item = items.find { it.id == id }
if (item != null) {
val updatedItem = item.copy(
quantity = quantity,
lastUpdated = Clock.System.now()
.toLocalDateTime(TimeZone.currentSystemDefault()).toString()
)
items[items.indexOf(item)] = updatedItem
println("Updated item: ${item.name} to quantity: $quantity")
} else {
println("Item not found")
}
}
fun promptForCommand(): String {
println("Enter a command (add, update, list, totalValue, exit):")
return readLine() ?: ""
}
fun handleAddCommand() {
val name = promptForItemName()
val quantity = promptForQuantity()
addItem(name, quantity)
}
fun handleUpdateCommand() {
val id = promptForItemId()
val quantity = promptForQuantity()
updateItem(id, quantity)
}
fun promptForItemName(): String {
println("Enter item name:")
return readLine() ?: ""
}
fun promptForQuantity(): Int {
println("Enter quantity:")
return readLine()?.toIntOrNull() ?: 0
}
fun promptForItemId(): Int {
println("Enter item ID:")
return readLine()?.toIntOrNull() ?: 0
}
fun main() {
while (true) {
val command = promptForCommand()
when (command) {
"add" -> handleAddCommand()
"update" -> handleUpdateCommand()
"exit" -> break
else -> println("Unknown command")
}
}
}
Preguntas
- ¿Qué parte del código crees que corresponde a la lógica de aplicación?
- ¿Qué parte del código puedes considerar como lógica de negocio?
- ¿Cómo refactorizarías el código para separar estos dos tipos de lógica? No es necesario implementar la separación, solo describe cómo lo harías.
Solución
-
Lógica de Aplicación:
- La lógica de aplicación se refiere a todo lo relacionado con la interacción con el usuario, gestión del flujo del programa y manejo de comandos.
- Esto incluye:
promptForCommand()
handleAddCommand()
handleUpdateCommand()
promptForItemName()
,promptForQuantity()
,promptForItemId()
- La función
main
, ya que es la encargada de coordinar todo el flujo de la aplicación y las interacciones con el usuario.
-
Lógica de Negocio:
- La lógica de negocio incluye las operaciones que involucran directamente las reglas de negocio, como la gestión de los items (agregar, actualizar, y la manipulación de los mismos).
- Esto incluye:
- La clase
Item
, que es parte del modelo de negocio. - Las funciones
addItem()
yupdateItem()
, ya que estas se encargan de procesar la lógica central de la aplicación (creación y modificación de items).
- La clase
-
Refactorización:
- Para separar la lógica de negocio de la lógica de aplicación, podríamos mover la lógica de negocio a una biblioteca separada o a otro módulo que maneje solo las operaciones de gestión de items.
- Podríamos hacer lo siguiente:
- Mover la clase
Item
y las funcionesaddItem()
yupdateItem()
a un módulo o archivo separado, por ejemplo, a un archivo llamadoItemService.kt
. - Mantener las funciones que manejan las interacciones con el usuario (
promptForCommand()
,handleAddCommand()
, etc.) en el archivo de la aplicación principal. - Esto permitiría que el módulo de negocio no dependa de la interfaz de usuario y que pueda ser probado o reutilizado en otros contextos sin necesidad de modificar la lógica de aplicación.
- Mover la clase
¿Qué aprendimos?
En esta sección hemos aprendido a separar la lógica de negocio de la lógica de aplicación. A través de este proceso, vimos cómo la lógica de negocio —que es independiente de la infraestructura— puede extraerse a una biblioteca independiente, haciéndola más reutilizable y fácil de mantener. Por otro lado, la lógica de aplicación permanece en el módulo principal de la aplicación, encargándose de manejar las interacciones con el sistema, como la entrada y salida de datos.
Ventajas clave:
- Modularidad: Al separar la lógica en distintos módulos, logramos un sistema más modular, lo que facilita la mantenibilidad y escalabilidad.
- Reutilización: Al tener la lógica de negocio en una biblioteca independiente, puede ser reutilizada en otras aplicaciones sin depender de una implementación específica.
- Facilidad de pruebas: Las pruebas unitarias sobre la lógica de negocio son más sencillas ya que no dependen de infraestructura como bases de datos o redes.
En el próximo artículo, veremos cómo compilar la biblioteca que hemos creado y cómo integrarla de manera efectiva con el módulo app
, permitiendo que nuestra aplicación use la lógica de negocio extraída en su forma de biblioteca.
Bibliografías Recomendadas
- 🌐 "Business Logic vs. Application Logic: The Key Differences You Need to Know | APIsec." Accedido: 11 de septiembre de 2024. [En línea]. Disponible en: https://www.apisec.ai/blog/business-logic-vs-application-logic