Saltar al contenido principal

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.xml describe el proyecto. Lo construiremos en la siguiente unidad.
  • src/main/java contiene el código de producción: el que forma la aplicación.
  • src/main/resources contiene recursos que la aplicación necesita al ejecutarse, como una configuración o una plantilla de texto.
  • src/test/java contiene código que comprueba el comportamiento de producción.
  • src/test/resources contiene 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:

  1. producción y test queden separados;
  2. las tres clases de producción usen los packages del ejemplo;
  3. cada recurso quede en el ámbito que lo utiliza;
  4. la raíz quede preparada para pom.xml y README.md;
  5. 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.