1. Introducción
1.1. Propósito
Este documento describe la arquitectura de software de la aplicación. Su objetivo es proporcionar una visión general de la estructura del sistema, los principios de diseño aplicados y los patrones arquitectónicos utilizados. Servirá como guía para el desarrollo, mantenimiento y evolución de la aplicación.
1.2. Alcance
La arquitectura descrita aplica a una aplicación de escritorio monolítica desarrollada en Java con la librería gráfica Swing. El diseño se centra en la escalabilidad, mantenibilidad y un alto grado de desacoplamiento entre sus componentes.
2. Visión Arquitectónica General
La aplicación se basa en una arquitectura Monolítica Modular, organizada mediante el principio Package-by-Layered-Feature.
- Monolítica: Toda la aplicación se despliega como una única unidad ejecutable (un fichero JAR).
- Modular: A pesar de ser un monolito, el código está organizado en módulos de negocio (o "features") que son cohesivos y están débilmente acoplados entre sí.
- Package-by-Layered-Feature: Es la estrategia de organización del código. La estructura de paquetes de primer nivel corresponde a las funcionalidades de negocio (ej. user, order). Dentro de cada funcionalidad, el código se organiza por capas técnicas (mvc, service, data, etc.).
Este enfoque combina la simplicidad de despliegue de un monolito con la escalabilidad y mantenibilidad de un diseño modular.
3. Estructura de Paquetes
La estructura de paquetes es el reflejo directo de la arquitectura.
com.losadagm.demo
├── config/ // Configuración global (ej. logging, propiedades).
├── shared/ // Clases de utilidad compartidas por toda la aplicación.
│ └── db/ // Infraestructura de base de datos (DatabaseManager).
├── main/ // Punto de entrada de la aplicación.
│ └── AppMain.java
└── user/ // << MÓDULO DE FUNCIONALIDAD "USER" >>
├── mvc/ // Capa de Presentación (Patrón MVC + Observer).
│ ├── UserModel.java
│ ├── UserView.java
│ └── UserController.java
├── UserFacade.java // Fachada pública del módulo.
└── internal/ // Implementación interna, no visible para otros módulos.
├── domain/ // Entidades de dominio (POJOs).
│ └── User.java
├── service/ // Lógica de negocio (Patrón Service Layer).
│ └── UserServiceImpl.java
└── data/ // Acceso a datos (Patrón DAO).
├── UserDao.java
└── UserDaoDbImpl.java
4. Descripción de las Capas y Patrones
4.1. Capa de Presentación (Presentation Layer)
Responsabilidad: Mostrar la interfaz de usuario (UI) y capturar las interacciones del usuario.
Patrones Clave: MVC (Model-View-Controller) y Observer.
- Model (ej.
UserModel.java
): Es un ViewModel que contiene el estado de la vista. No contiene lógica de negocio. Es unObservable
. - View (ej.
UserView.java
): UnJPanel
oJFrame
que muestra los datos del Modelo. Es unObserver
que se actualiza automáticamente cuando el Modelo cambia. - Controller (ej.
UserController.java
): Recibe eventos de la Vista y orquesta la respuesta, llamando a la Fachada de negocio y actualizando el Modelo.
4.2. Capa de Servicio (Service Layer)
Responsabilidad: Contener la lógica de negocio central de la aplicación.
Patrones Clave: Fachada (Facade) y Service Layer.
- Facade (ej.
UserFacade.java
): Es el punto de entrada público y simplificado a la funcionalidad del módulo. - Service Layer (ej.
UserServiceImpl.java
): Implementa las reglas de negocio específicas.
4.3. Capa de Datos (Data Layer)
Responsabilidad: Abstraer el acceso a la fuente de datos (JDBC).
Patrones Clave: DAO (Data Access Object).
- DAO Interface (ej.
UserDao.java
): Define el contrato para las operaciones de persistencia (CRUD). - DAO Implementation (ej.
UserDaoDbImpl.java
): Implementa el contrato usando SQL y elDatabaseManager
. - Infraestructura: El
DatabaseManager
gestiona el pool de conexiones (C3P0) y las transacciones.
5. Flujo de Datos y Control
Un flujo de interacción típico (ej. refrescar una lista de usuarios) sigue estos pasos:
- View: El usuario hace clic en el botón "Refrescar". La
UserView
notifica alUserController
. - Controller: El
UserController
llama al métodogetAllUsers()
de laUserFacade
. - Facade: La
UserFacade
delega la llamada a suUserService
interno. - Service: El
UserServiceImpl
llama al métodofindAll()
de suUserDao
. - DAO: El
UserDaoDbImpl
ejecuta la consulta SQL a través delDatabaseManager
. - Retorno: Los datos viajan de vuelta por las mismas capas, el
UserController
actualiza elUserModel
. - Observer: El
UserModel
notifica a suObserver
(laUserView
) que su estado ha cambiado. - View: La
UserView
se actualiza, redibujando la tabla con los nuevos datos.
6. Principios de Diseño Clave
- Separación de Responsabilidades (SoC): Cada capa y clase tiene una única responsabilidad bien definida.
- Inversión de Dependencias (DIP): Las capas de alto nivel dependen de abstracciones (interfaces), no de implementaciones concretas.
- Encapsulación y Ocultación de Información: Los paquetes
internal
ocultan detalles de implementación, forzando la comunicación a través de fachadas públicas.
Apéndice A: Aplicación Práctica de la Arquitectura - Caso de Uso
A.1. Escenario del Caso de Uso
Se considera un escenario donde la aplicación tiene una interfaz de usuario (UI) simple, consistente en un único panel de control (Dashboard), pero maneja una lógica de negocio compleja que involucra múltiples entidades de dominio (User, Product, Order).
Requisito del caso de uso: El usuario hace clic en un botón "Crear Nuevo Pedido" en el Dashboard. Esta acción requiere obtener información del usuario, verificar el stock del producto y finalmente, crear un nuevo pedido en la base de datos.
A.2. Estructura de Paquetes en este Escenario
En este caso, la arquitectura se manifiesta de la siguiente manera: solo la funcionalidad del Dashboard tiene una capa de presentación mvc. Las demás funcionalidades (user, product, order) exponen su lógica a través de fachadas, pero no tienen vistas propias.
com.losadagm.demo
│
├── dashboard/ // Módulo con UI.
│ ├── mvc/
│ │ ├── DashboardModel.java
│ │ ├── DashboardView.java
│ │ └── DashboardController.java
│ └── DashboardFacade.java
│
├── user/ // Módulo "sin cabeza" (headless).
│ ├── UserFacade.java
│ └── internal/
│
├── product/ // Módulo "sin cabeza".
│ ├── ProductFacade.java
│ └── internal/
│
└── order/ // Módulo "sin cabeza".
├── OrderFacade.java
└── internal/
A.3. Flujo de Control Detallado
El flujo para "Crear Nuevo Pedido" demuestra cómo las distintas funcionalidades colaboran sin acoplarse directamente, orquestadas por el DashboardController y las fachadas.
Evento de UI (DashboardView):
El usuario introduce los datos del pedido (ID de usuario, SKU del producto, cantidad) y hace clic en "Crear Pedido". La DashboardView
captura el evento y llama a su controlador: controller.handleCreateOrderRequest(userId, productSku, quantity);
.
Orquestación en el Controlador (DashboardController):
El DashboardController
recibe la petición. Su rol no es saber cómo se crea un pedido, sino delegar la tarea a los expertos. Invoca a la fachada de pedidos: orderFacade.createNewOrder(userId, productSku, quantity);
.
Lógica de Negocio en la Fachada (OrderFacade):
OrderFacade.createNewOrder()
es el método que orquesta la operación completa. Para ello, colabora con otras fachadas, pero nunca con sus detalles internos.
- Paso 1: Validar Usuario. Llama a la fachada de usuarios:
Optional<UserDTO> user = userFacade.findUserById(userId);
. Si el usuario no existe, la operación falla. - Paso 2: Verificar Stock. Llama a la fachada de productos:
boolean hasStock = productFacade
.checkStockAvailability(productSku, quantity);
. Si no hay stock, la operación falla. - Paso 3: Ejecutar Lógica Interna. Si las validaciones son correctas, la
OrderFacade
llama a su propio servicio interno (orderService
) para que realice el trabajo de bajo nivel: crear la entidadOrder
, actualizar el stock y guardar todo en la base de datos dentro de una única transacción.
Actualización de la UI:
La OrderFacade
devuelve un resultado. El DashboardController
lo recibe y le pide a la DashboardFacade
que refresque los datos del panel. Esta fachada obtiene los datos actualizados, los pone en el DashboardModel
, y el modelo notifica a la DashboardView
(su Observer) para que se redibuje y muestre el estado más reciente.
A.4. Conclusiones del Caso de Uso
Este ejemplo demuestra los beneficios clave de la arquitectura:
- Desacoplamiento: El módulo
order
no sabe nada sobre la existencia de un Dashboard. Su única puerta de entrada esOrderFacade
. - Cohesión: Toda la lógica para crear un pedido está contenida dentro del módulo
order
. - Simplicidad en la UI: El
DashboardController
es simple y legible, ya que solo contiene llamadas de alto nivel a las fachadas. - Escalabilidad: Añadir una nueva funcionalidad (ej.
Invoice
) simplemente requiere crear un nuevo módulo con su propia fachada.