Aprendiendo SOLID con Java y Servlets clásicos: Separando DAO, Servicio y Controlador
Cuando comencé a desarrollar aplicaciones web en Java, usaba JSPs y Servlets sin mucho orden. Todo el código vivía en el Servlet: SQL, lógica, control de errores, todo junto. Con el tiempo me di cuenta de que esa forma de trabajar era insostenible. Así que decidí aprender algo de arquitectura.
Hoy quiero compartir cómo estructuré una aplicación MVC clásica en Java, aplicando principios de diseño SOLID, sin usar Spring.
El enfoque me permitió tener un sistema más limpio, extensible y fácil de mantener.
📈 ¿Qué es MVC clásico?
- Modelo: representa los datos de negocio (
Usuario
). - Vista: JSP que muestra los datos (
usuario.jsp
). - Controlador: Servlet que recibe la solicitud y coordina la respuesta.
🤔 ¿Qué principios SOLID estoy aplicando?
Principio | Cómo lo aplico |
---|---|
S – Responsabilidad única | Cada clase hace solo una cosa (Servlet, Servicio, DAO, Modelo). |
O – Abierto/cerrado | Puedo extender funcionalidades sin modificar clases existentes. |
L – Sustitución de Liskov | El servicio puede usar cualquier implementación de UsuarioDAO . |
I – Segregación de interfaces | Cada interfaz expone solo lo necesario (en este caso, una interfaz DAO bien delimitada). |
D – Inversión de dependencias | El servicio depende de la interfaz, no de la implementación concreta. |
📏 Estructura del proyecto
com.miapp.model.Usuario
com.miapp.dao.UsuarioDAO
com.miapp.dao.impl.UsuarioDAOImpl
com.miapp.service.UsuarioService
com.miapp.factory.ServiceFactory
com.miapp.web.UsuarioServlet
📦 Modelo: Usuario.java
package com.miapp.model;
public class Usuario {
private int id;
private String nombre;
private String email;
public Usuario(int id, String nombre, String email) {
this.id = id;
this.nombre = nombre;
this.email = email;
}
public int getId() { return id; }
public String getNombre() { return nombre; }
public String getEmail() { return email; }
}
📁 Interfaz DAO: UsuarioDAO.java
package com.miapp.dao;
import com.miapp.model.Usuario;
import java.util.List;
public interface UsuarioDAO {
Usuario obtenerPorId(int id);
List<Usuario> listarTodos();
void guardar(Usuario usuario);
void eliminar(int id);
}
🔧 Implementación DAO: UsuarioDAOImpl.java
package com.miapp.dao.impl;
import com.miapp.dao.UsuarioDAO;
import com.miapp.model.Usuario;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
public class UsuarioDAOImpl implements UsuarioDAO {
private Connection abrirConexion() throws SQLException {
return DriverManager.getConnection("jdbc:mysql://localhost:3306/miapp", "usuario", "clave");
}
@Override
public Usuario obtenerPorId(int id) {
try (Connection conn = abrirConexion();
PreparedStatement ps = conn.prepareStatement("SELECT * FROM usuarios WHERE id = ?")) {
ps.setInt(1, id);
try (ResultSet rs = ps.executeQuery()) {
if (rs.next()) {
return new Usuario(rs.getInt("id"), rs.getString("nombre"), rs.getString("email"));
}
}
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
// Implementar listarTodos(), guardar(), eliminar() de forma similar
}
🧠 Servicio: UsuarioService.java
package com.miapp.service;
import com.miapp.dao.UsuarioDAO;
import com.miapp.model.Usuario;
import java.util.List;
public class UsuarioService {
private final UsuarioDAO dao;
public UsuarioService(UsuarioDAO dao) {
this.dao = dao;
}
public Usuario obtenerPorId(int id) {
return dao.obtenerPorId(id);
}
public List<Usuario> listarTodos() {
return dao.listarTodos();
}
public void guardarUsuario(Usuario usuario) {
dao.guardar(usuario);
}
public void eliminarUsuario(int id) {
dao.eliminar(id);
}
}
🏭 Fábrica: ServiceFactory.java
package com.miapp.factory;
import com.miapp.dao.UsuarioDAO;
import com.miapp.dao.impl.UsuarioDAOImpl;
import com.miapp.service.UsuarioService;
public class ServiceFactory {
public static UsuarioService getUsuarioService() {
UsuarioDAO dao = new UsuarioDAOImpl();
return new UsuarioService(dao);
}
}
🌐 Servlet: UsuarioServlet.java
package com.miapp.web;
import com.miapp.model.Usuario;
import com.miapp.service.UsuarioService;
import com.miapp.factory.ServiceFactory;
import com.google.gson.Gson;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.*;
import java.io.IOException;
@WebServlet("/usuario")
public class UsuarioServlet extends HttpServlet {
private final UsuarioService service = ServiceFactory.getUsuarioService();
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
String idParam = req.getParameter("id");
if (idParam == null) {
resp.sendError(HttpServletResponse.SC_BAD_REQUEST, "Falta parámetro id");
return;
}
int id = Integer.parseInt(idParam);
Usuario usuario = service.obtenerPorId(id);
if (usuario == null) {
resp.sendError(HttpServletResponse.SC_NOT_FOUND, "Usuario no encontrado");
return;
}
resp.setContentType("application/json;charset=UTF-8");
resp.getWriter().write(new Gson().toJson(usuario));
}
}
🗺️ Conclusión
Aplicar principios SOLID no requiere Spring ni frameworks modernos.
Puedes hacerlo perfectamente en una aplicación Java clásica, usando Servlets y JSP, simplemente organizando bien tus capas y separando responsabilidades.
Esto me permite mantener el código limpio, probar con distintas implementaciones (por ejemplo, JDBC o Hibernate) y estar listo para crecer hacia arquitecturas más complejas sin reescribir desde cero.
📅 ¿Y tú?
¿Trabajas con Java web tradicional? ¿Te gustaría ver este proyecto en GitHub?
Comenta, comparte tu experiencia y conversemos.