CodeGym /Cursos /JAVA 25 SELF /Separación del proyecto en módulos: buenas prácticas

Separación del proyecto en módulos: buenas prácticas

JAVA 25 SELF
Nivel 60, Lección 3
Disponible

1. Cuándo y por qué dividir un proyecto en módulos

Por qué no conviene hacerlo todo en un único módulo

Si escribes una pequeña práctica de laboratorio o "Hello, World!", el sistema modular puede parecer excesivo. Pero a medida que el proyecto crece —decenas y cientos de clases, multitud de paquetes, bibliotecas de terceros— el caos se vuelve inevitable. Es como una biblioteca sin estanterías: mientras hay pocos libros, es pasable, pero luego es difícil encontrar algo. Los módulos son tus estanterías: ayudan a poner orden y ocultan la «cocina» (la implementación), dejando fuera solo el «escaparate» (API).

Para qué dividir en módulos

  • Separación de responsabilidades: cada módulo se hace cargo de su ámbito (por ejemplo, BD, lógica de negocio, UI).
  • Reutilización de código: un módulo puede conectarse en otro proyecto.
  • Mejora de la capacidad de prueba: los módulos se prueban de forma independiente.
  • Seguridad e encapsulación: hacia fuera solo se ve el API; la implementación queda oculta.
  • Facilita el mantenimiento: menos dependencias «mágicas», un mapa de dependencias claro.
  • Compilación y despliegue más rápidos: solo se reconstruyen los módulos que han cambiado.

Cuándo dividir en módulos

  • El proyecto se vuelve demasiado grande para un solo desarrollador (o la IDE empieza a ir lenta).
  • Se distinguen claramente partes: core, ui, utils, api, impl.
  • Se planea reutilizar código en otros proyectos.
  • Hay dependencias externas que solo necesita una parte del proyecto.
  • Es necesario ocultar detalles de implementación (algoritmos, clases internas).

2. Esquemas típicos de modularidad

A continuación encontrarás esquemas de división populares, adecuados tanto para proyectos de aprendizaje como para proyectos reales.

Arquitectura «Onion» (Onion Architecture)

La capa externa depende de la interna, pero no al revés.

[ app (UI) ]
     ↓
[ core (lógica) ]
     ↓
[ utils (utilidades) ]
  • app — módulo externo: interfaz gráfica, aplicación web, punto de entrada (main).
  • core — lógica de negocio, modelos, servicios.
  • utils — clases auxiliares.

Regla: la capa interna no debe depender de la externa. Así core se puede reutilizar en distintas interfaces (consola, web, escritorio).

Módulos para API e implementación

Para bibliotecas conviene separar las interfaces de su implementación:

[ mylib.api ]  ← exporta solo interfaces
[ mylib.impl ] ← contiene implementaciones, no se exporta

Módulos para tests

A menudo se separan los tests en un módulo aparte para que no terminen en el artefacto de producción.

[ app ]
[ core ]
[ core.tests ]

Esquema para un proyecto didáctico

myeditor/
  ├─ app/         ← punto de entrada, arranque de la aplicación
  ├─ core/        ← lógica de negocio (trabajo con archivos y texto)
  └─ utils/       ← utilidades (logging, análisis/parsing)

3. Dependencias entre módulos

En los módulos de Java, las dependencias se especifican explícitamente en module-info.java mediante la palabra clave requires. Esto mejora la legibilidad y permite al compilador/JVM controlar la accesibilidad del API mediante exports.

Ejemplo de dependencia

core/module-info.java

module myeditor.core {
    exports myeditor.core.api; // solo es visible hacia fuera el paquete api
    requires myeditor.utils;   // usamos utilidades
}

app/module-info.java

module myeditor.app {
    requires myeditor.core;    // usamos core
    requires myeditor.utils;   // podemos usar utilidades directamente
}

Reglas y buenas prácticas

  • Evita las dependencias cíclicas. Si A requires B y B requires A, es un defecto de diseño. Normalmente se resuelve extrayendo un common/api común.
  • Minimiza las dependencias. No añadas un módulo si realmente no hace falta.
  • Exporta los paquetes utilizados. Las clases deben estar en paquetes declarados con exports; de lo contrario, habrá error de compilación.
  • Utilidades: lo más independientes posible. utils no debe depender de la lógica de negocio.

4. Práctica: ejemplo de división de un proyecto didáctico en 3 módulos

Estructura de carpetas

myeditor/
  ├─ app/
  │    ├─ src/
  │    │    └─ myeditor/app/Main.java
  │    └─ module-info.java
  ├─ core/
  │    ├─ src/
  │    │    ├─ myeditor/core/api/TextService.java
  │    │    └─ myeditor/core/impl/TextServiceImpl.java
  │    └─ module-info.java
  └─ utils/
       ├─ src/
       │    └─ myeditor/utils/Logger.java
       └─ module-info.java

Ejemplos de module-info.java

core/module-info.java

module myeditor.core {
    exports myeditor.core.api;
    requires myeditor.utils;
}

app/module-info.java

module myeditor.app {
    requires myeditor.core;
    requires myeditor.utils;
}

utils/module-info.java

module myeditor.utils {
    exports myeditor.utils;
}

Ejemplo de código (TextService)

myeditor/core/api/TextService.java

package myeditor.core.api;

public interface TextService {
    String toUpperCase(String text);
}

myeditor/core/impl/TextServiceImpl.java

package myeditor.core.impl;

import myeditor.core.api.TextService;

public class TextServiceImpl implements TextService {
    @Override
    public String toUpperCase(String text) {
        return text.toUpperCase();
    }
}

myeditor/app/Main.java

package myeditor.app;

import myeditor.core.api.TextService;
import myeditor.core.impl.TextServiceImpl;

public class Main {
    public static void main(String[] args) {
        TextService service = new TextServiceImpl();
        System.out.println(service.toUpperCase("hello, modules!"));
    }
}

Cómo se ve en IntelliJ IDEA

  • Cada directorio es un módulo independiente en la estructura del proyecto.
  • Cada módulo tiene su propio module-info.java en la raíz de src.
  • Al ejecutar main desde app, la IDE ajustará por sí sola el module-path.
  • Intentar usar una clase de un paquete no exportado terminará con un error de compilación.

5. Impacto en la compilación: Maven/Gradle y módulos

Maven

Un proyecto multimódulo consta de un proyecto «padre» (parent) y varios módulos «hijo».

myeditor/
  ├─ pom.xml        ← parent
  ├─ app/
  │    └─ pom.xml
  ├─ core/
  │    └─ pom.xml
  └─ utils/
       └─ pom.xml

Ejemplo de parent pom.xml

<project>
  <modelVersion>4.0.0</modelVersion>
  <groupId>myeditor</groupId>
  <artifactId>myeditor-parent</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>pom</packaging>

  <modules>
    <module>app</module>
    <module>core</module>
    <module>utils</module>
  </modules>
</project>

Particularidades:

  • Maven tiene en cuenta module-info.java durante la compilación.
  • Para ejecutar se usa --module-path en lugar de --classpath.
  • Si olvidas exports o requires, obtendrás un error de compilación.

Gradle

La multimodularidad se configura mediante settings.gradle y un build.gradle por módulo.

settings.gradle

rootProject.name = 'myeditor'
include 'app', 'core', 'utils'

build.gradle del módulo

plugins {
    id 'java'
}

java {
    modularity.inferModulePath = true
}

IntelliJ IDEA

  • IDEA puede crear module-info.java al crear un módulo de Java.
  • Con Maven/Gradle la estructura de módulos se detecta automáticamente.
  • Al ejecutar main desde app, la IDE configurará module-path.
  • Los cuadros de diálogo de import/export ayudan con la visibilidad de paquetes y módulos.

Errores típicos al dividir en módulos

Error n.º 1: Dependencias cíclicas entre módulos. Si dos módulos declaran requires uno del otro, el compilador dará error. Normalmente es señal de una arquitectura degradada. Solución: extraer un módulo api común o replantear los límites.

Error n.º 2: Uso de clases de paquetes no exportados. Una clase puede ser public, pero si el paquete no está indicado en exports en module-info.java, otro módulo no la verá. Resultado: error de compilación.

Error n.º 3: Olvidar añadir requires para el módulo utilizado. Importar desde otro módulo sin la entrada correspondiente en module-info.java no compilará. Declara siempre las dependencias de forma explícita.

Error n.º 4: Duplicación de nombres de módulos. Los nombres de los módulos deben ser únicos dentro de la compilación (especialmente con Maven/Gradle). Los duplicados rompen la compilación.

Error n.º 5: Estructura de directorios incorrecta. El archivo module-info.java debe estar en la raíz de src del módulo correspondiente. De lo contrario, el compilador no encontrará el módulo.

Error n.º 6: module-path incorrecto al ejecutar. Al ejecutar manualmente, especifica --module-path en lugar de --classpath; si no, verás «module not found».

1
Tarea
JAVA 25 SELF, nivel 60, lección 3
Bloqueada
Apertura de un canal de comunicación para el registro de eventos 📢
Apertura de un canal de comunicación para el registro de eventos 📢
1
Tarea
JAVA 25 SELF, nivel 60, lección 3
Bloqueada
Arquitecto de la agenda de direcciones digital 🗺️
Arquitecto de la agenda de direcciones digital 🗺️
Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION