Estructura de un proyecto Java real
Hasta ahora hemos podido aprender con uno o varios archivos cerca entre sí. Pero cuando el proyecto crece aparece una pregunta inevitable: tengo varios .java, ¿dónde va cada cosa?
Una estructura compartida evita que cada persona tenga que adivinar dónde está el código, qué archivos necesita la aplicación o cuáles son solo pruebas. También permite que las herramientas trabajen con el proyecto sin depender de una configuración particular del editor.
Las partes del proyecto
Usaremos la estructura convencional de Maven, aunque todavía no estudiaremos su ciclo de build:
proyecto/
├── pom.xml
└── src/
├── main/
│ ├── java/
│ │ └── ...
│ └── resources/
└── test/
├── java/
│ └── ...
└── resources/
pom.xmldescribe el proyecto. Lo construiremos en la siguiente unidad.src/main/javacontiene el código de producción: el que forma la aplicación.src/main/resourcescontiene recursos que la aplicación necesita al ejecutarse, como una configuración o una plantilla de texto.src/test/javacontiene código que comprueba el comportamiento de producción.src/test/resourcescontiene datos exclusivos de esas comprobaciones.
El código de test no se mezcla con producción. Así no acaba dentro de la aplicación por accidente y puede utilizar recursos preparados específicamente para probar casos concretos.
Packages y directorios
El package identifica el espacio al que pertenece una clase. Desde la raíz de fuentes, cada parte del nombre corresponde a un directorio:
package es.skilly.tareas.modelo;
debe estar en:
src/main/java/es/skilly/tareas/modelo/Tarea.java
Los nombres de package se escriben en minúsculas. Una práctica habitual es comenzar con un dominio invertido y añadir el proyecto y la responsabilidad: es.skilly.tareas.servicio.
La correspondencia también se mantiene en tests. Un test para una clase de es.skilly.tareas.servicio se sitúa bajo:
src/test/java/es/skilly/tareas/servicio/
Un ejemplo organizado
TasteMatch evoluciona ahora hacia un pequeño gestor de tareas. Queremos separar el punto de entrada, el modelo y la lógica que coordina las tareas:
gestor-tareas/
├── pom.xml
└── src/
├── main/
│ ├── java/
│ │ └── es/skilly/tareas/
│ │ ├── app/Main.java
│ │ ├── modelo/Tarea.java
│ │ └── servicio/GestorTareas.java
│ └── resources/
│ └── mensajes.properties
└── test/
├── java/
│ └── es/skilly/tareas/servicio/
└── resources/
└── tareas-ejemplo.txt
Tarea.java representa los datos:
package es.skilly.tareas.modelo;
public class Tarea {
private final String descripcion;
private boolean completada;
public Tarea(String descripcion) {
if (descripcion == null || descripcion.isBlank()) {
throw new IllegalArgumentException("La descripción es obligatoria");
}
this.descripcion = descripcion;
}
public String getDescripcion() {
return descripcion;
}
public boolean isCompletada() {
return completada;
}
public void completar() {
completada = true;
}
}
GestorTareas.java reúne la lógica de la aplicación:
package es.skilly.tareas.servicio;
import es.skilly.tareas.modelo.Tarea;
import java.util.ArrayList;
import java.util.List;
public class GestorTareas {
private final List<Tarea> tareas = new ArrayList<>();
public void agregar(Tarea tarea) {
tareas.add(tarea);
}
public int cantidad() {
return tareas.size();
}
}
Main.java es el punto de entrada y conecta las piezas:
package es.skilly.tareas.app;
import es.skilly.tareas.modelo.Tarea;
import es.skilly.tareas.servicio.GestorTareas;
public class Main {
public static void main(String[] args) {
GestorTareas gestor = new GestorTareas();
gestor.agregar(new Tarea("Preparar el proyecto"));
System.out.println("Tareas: " + gestor.cantidad());
}
}
Desde la raíz podemos comprobar la organización sin depender de un IDE:
javac -d target/classes \
src/main/java/es/skilly/tareas/modelo/Tarea.java \
src/main/java/es/skilly/tareas/servicio/GestorTareas.java \
src/main/java/es/skilly/tareas/app/Main.java
java -cp target/classes es.skilly.tareas.app.Main
La opción -d deja las clases compiladas en target/classes. En un proyecto Maven, target/ es la salida generada del build: no es código fuente, puede reconstruirse y normalmente se añade a .gitignore.
Recursos no son clases
Un .properties, un texto inicial o una plantilla no debe colocarse junto a los .java. Los recursos de ejecución pertenecen a src/main/resources; los datos diseñados solo para pruebas, a src/test/resources.
Tampoco todo archivo debe convertirse en recurso. El código sigue en src/main/java, y los documentos para las personas —por ejemplo README.md— suelen permanecer en la raíz.
Mini reto: ordenar un proyecto plano
Partimos de esta estructura:
gestor-tareas/
├── Main.java
├── Tarea.java
├── GestorTareas.java
├── GestorTareasTest.java
├── mensajes.properties
└── tareas-prueba.txt
Reorganízala de modo que:
- producción y test queden separados;
- las tres clases de producción usen los packages del ejemplo;
- cada recurso quede en el ámbito que lo utiliza;
- la raíz quede preparada para
pom.xmlyREADME.md; target/se considere salida regenerable.
Solución
gestor-tareas/
├── pom.xml
├── README.md
└── src/
├── main/
│ ├── java/es/skilly/tareas/
│ │ ├── app/Main.java
│ │ ├── modelo/Tarea.java
│ │ └── servicio/GestorTareas.java
│ └── resources/mensajes.properties
└── test/
├── java/es/skilly/tareas/servicio/GestorTareasTest.java
└── resources/tareas-prueba.txt
La solución no cambia el comportamiento: hace explícita la responsabilidad de cada archivo. En la siguiente unidad dejaremos de compilar las clases una a una y describiremos un build reproducible mediante Maven.