Patrones de Desarrollo tinystruct
Patrones de arquitectura e implementación para construir módulos con el framework Java tinystruct – un framework ligero y de alto rendimiento que trata CLI y HTTP como ciudadanos iguales, sin necesidad de un método main() y con configuración mínima.
Principio Fundamental
CLI y HTTP son ciudadanos iguales. Cada método anotado con @Action debería, idealmente, poder ejecutarse tanto desde un terminal como desde un navegador web sin modificaciones. Esta capacidad de "modo dual" es la filosofía de diseño central de tinystruct.
Cuándo Activarlo
Cuándo Usarlo
- Crear nuevos módulos
ApplicationextendiendoAbstractApplication. - Definir rutas y acciones de línea de comandos usando
@Action. - Manejar el estado por solicitud mediante
Context. - Realizar serialización JSON usando los componentes nativos
BuilderyBuilders. - Trabajar con persistencia de bases de datos a través de POJOs
AbstractData. - Generar POJOs desde tablas de base de datos usando el comando
generate. - Implementar Server-Sent Events (SSE) para push en tiempo real.
- Manejar carga de archivos mediante datos multiparte.
- Realizar solicitudes HTTP salientes con
URLRequestyHTTPHandler. - Configurar conexiones de base de datos o ajustes del sistema en
application.properties. - Depurar conflictos de enrutamiento (Actions) o análisis de argumentos CLI.
Cómo Funciona
El framework tinystruct trata cualquier método anotado con @Action como un punto final enrutable tanto para el terminal como para entornos web. Las aplicaciones se crean extendiendo AbstractApplication, que proporciona hooks de ciclo de vida centrales como init() y acceso al Context de la solicitud.
El enrutamiento es gestionado por ActionRegistry, que mapea automáticamente segmentos de ruta a argumentos de método e inyecta dependencias. Para servicios solo de datos, se deben usar los componentes nativos Builder y Builders para la serialización JSON, manteniendo una huella de cero dependencias. La capa de base de datos utiliza POJOs AbstractData emparejados con archivos de mapeo XML para operaciones CRUD sin bibliotecas ORM externas.
Ejemplos
Aplicación Básica (MyService)
public class MyService extends AbstractApplication {
@Override
public void init() {
this.setTemplateRequired(false); // Deshabilitar búsqueda .view para aplicaciones de datos/API
}
@Override public String version() { return "1.0.0"; }
@Action("saludar")
public String saludar() {
return "¡Hola desde tinystruct!";
}
// Parámetro de ruta: GET /?q=saludar/Juan O bin/dispatcher saludar/Juan
@Action("saludar")
public String saludar(String nombre) {
return "¡Hola, " + nombre + "!";
}
}
Desambiguación de Modo HTTP (login)
@Action(value = "iniciarSesion", mode = Mode.HTTP_POST)
public String doLogin(Request<?, ?> request) throws ApplicationException {
request.getSession().setAttribute("userId", "42");
return "Sesión iniciada";
}
Manejo de Datos JSON Nativo (Builder + Builders)
import org.tinystruct.data.component.Builder;
import org.tinystruct.data.component.Builders;
@Action("api/datos")
public String getData() throws ApplicationException {
Builders listaDatos = new Builders();
Builder elemento = new Builder();
elemento.put("id", 1);
elemento.put("nombre", "Juan");
listaDatos.add(elemento);
Builder respuesta = new Builder();
respuesta.put("estado", "éxito");
respuesta.put("datos", listaDatos);
return respuesta.toString(); // {"estado":"éxito","datos":[{"id":1,"nombre":"Juan"}]}
}
SSE (Server-Sent Events)
import org.tinystruct.http.SSEPushManager;
@Action("sse/conectar")
public String conectar() {
return "{\"type\":\"connect\",\"message\":\"Conectado a SSE\"}";
}
// Enviar a un cliente específico
String idSesion = getContext().getId();
Builder mensaje = new Builder();
mensaje.put("texto", "¡Hola, usuario!");
SSEPushManager.getInstance().push(idSesion, mensaje);
// Transmitir a todos
SSEPushManager.getInstance().broadcast(mensaje);
Carga de Archivos
import org.tinystruct.data.FileEntity;
@Action(value = "subir", mode = Mode.HTTP_POST)
public String subir(Request<?, ?> request) throws ApplicationException {
List<FileEntity> archivos = request.getAttachments();
if (archivos != null) {
for (FileEntity archivo : archivos) {
System.out.println("Subido: " + archivo.getFilename());
}
}
return "Carga OK";
}
Integración del Servidor MCP y Herramientas
tinystruct proporciona soporte nativo para el Protocolo de Contexto de Modelo (MCP) a partir de la versión del SDK 1.7.26.
Las APIs de MCP (ej., org.tinystruct.mcp.MCPTool, org.tinystruct.mcp.MCPServer, org.tinystruct.mcp.MCPException) están incluidas directamente en la dependencia principal:
<dependency>
<groupId>org.tinystruct</groupId>
<artifactId>tinystruct</artifactId>
<version>1.7.26</version>
</dependency>
ADVERTENCIA DE SEGURIDAD (Inyección de Prompts): Los valores de retorno de las herramientas se alimentan directamente al contexto del modelo de IA. DEBES validar y sanitizar todos los argumentos proporcionados por el llamante antes de incluirlos en la cadena de retorno de la herramienta. No sanitizar las entradas puede permitir que un atacante inyecte instrucciones adversarias (Inyección de Prompts) que anulen el comportamiento del modelo. Siempre valida la longitud, los conjuntos de caracteres y la nulidad.
Para crear una Herramienta MCP:
- Extiende
org.tinystruct.mcp.MCPTool. - Anota las operaciones con
@Actiony declara los parámetros usando@Argumentdentro del arrayarguments. - Acepta los parámetros como argumentos explícitos del método que coincidan con las claves en
@Argument. (No usesgetContext().getAttribute(...)para argumentos de herramientas).
import org.tinystruct.mcp.MCPTool;
import org.tinystruct.mcp.MCPException;
import org.tinystruct.system.annotation.Action;
import org.tinystruct.system.annotation.Argument;
public class MiHerramientaPersonalizada extends MCPTool {
public MiHerramientaPersonalizada() {
super("personalizada", "Una herramienta personalizada para demostrar MCP");
}
@Action(
value = "personalizada/hola",
description = "Saludar a alguien",
arguments = {
@Argument(key = "nombre", description = "El nombre a saludar", type = "string", optional = false)
}
)
public String hola(String nombre) throws MCPException {
// SEGURIDAD: Validar/sanitizar las entradas de la herramienta antes de devolverlas al modelo
// para prevenir vulnerabilidades de inyección de prompts.
if (nombre == null || nombre.length() > 50 || !nombre.matches("^[a-zA-ZáéíóúÁÉÍÓÚñÑ0-9 ]+$")) {
throw new MCPException("Nombre no válido");
}
return "¡Hola, " + nombre + "!";
}
}
Para desplegar un Servidor MCP:
- Extiende
org.tinystruct.mcp.MCPServer. - Sobrescribe
init()y registra tus herramientas usandothis.registerTool(). El framework escanea y mapea automáticamente los métodos@Action.
import org.tinystruct.mcp.MCPServer;
public class MiServidorMCP extends MCPServer {
@Override
public void init() {
super.init();
this.registerTool(new MiHerramientaPersonalizada());
}
@Override
public String version() {
return "1.0.0";
}
}
Ejecuta el servidor a través del dispatcher:
bin/dispatcher start --import org.tinystruct.system.HttpServer --import com.ejemplo.MiServidorMCP
Configuración
Los ajustes se gestionan en src/main/resources/application.properties.
# Base de datos
driver=org.h2.Driver
database.url=jdbc:h2:~/mibd
database.user=sa
database.password=
# Servidor
default.home.page=hola
server.port=8080
# Locale
default.language=es_MX
# Sesión (Redis para entornos en clúster)
# default.session.repository=org.tinystruct.http.RedisSessionRepository
# redis.host=127.0.0.1
# redis.port=6379
Accede a los valores de configuración en tu aplicación:
String puerto = this.getConfiguration("server.port");
Banderas Rojas y Antipatrones
| Síntoma | Patrón Correcto |
|---|---|
Importar com.google.gson o com.fasterxml.jackson | Usar org.tinystruct.data.component.Builder / Builders. |
Usar List<Builder> para arrays JSON | Usar Builders para evitar problemas de borrado de tipos genéricos. |
ApplicationRuntimeException: template not found | Llamar setTemplateRequired(false) en init() para aplicaciones solo API. |
Anotar métodos private con @Action | Las acciones deben ser public para ser registradas por el framework. |
Hardcodear main(String[] args) en aplicaciones | Usar bin/dispatcher como punto de entrada para todos los módulos. |
Registro manual de ActionRegistry | Preferir la anotación @Action para descubrimiento automático. |
| Acción no encontrada en tiempo de ejecución | Asegurar que la clase se importe mediante --import o se liste en application.properties. |
| Argumento CLI no visible | Pasar con --clave valor; acceder mediante getContext().getAttribute("--clave"). |
| Dos métodos misma ruta, se ejecuta el incorrecto | Establecer mode explícito (ej., HTTP_GET vs HTTP_POST) para desambiguar. |
Mejores Prácticas
- Aplicaciones Granulares: Divide la lógica en aplicaciones más pequeñas y enfocadas en lugar de una clase monolítica.
- Configuración en
init(): Aprovechainit()para la configuración (config, BD) en lugar del constructor. NO llames asetAction()— usa la anotación@Action. - Conciencia de Modo: Usa el parámetro
Modeen@Actionpara restringir operaciones sensibles solo aCLIo a métodos HTTP específicos. - Contexto sobre Parámetros: Para banderas CLI opcionales, usa
getContext().getAttribute("--bandera")en lugar de añadir parámetros a la firma del método. - Eventos Asíncronos: Para tareas pesadas desencadenadas por eventos, usa
CompletableFuture.runAsync()dentro del manejador de eventos.
Referencia Técnica
Las guías detalladas están disponibles en el directorio references/:
- Arquitectura y Configuración — Abstracciones, Mapa de Paquetes, Propiedades
- Enrutamiento y @Action — Detalles de la anotación, Modos, Parámetros
- Manejo de Datos — Builder, Builders, serialización y análisis JSON
- Persistencia de Base de Datos — POJOs AbstractData, CRUD, XML de mapeo, generación de POJOs
- Sistema y Uso — Contexto, Sesiones, SSE, Carga de Archivos, Eventos, Redes
- Patrones de Prueba — Pruebas unitarias JUnit 5 y de integración HTTP
Archivos de Referencia (Internos)
src/main/java/org/tinystruct/AbstractApplication.java— Clase base central con hooks de ciclo de vidasrc/main/java/org/tinystruct/system/annotation/Action.java— Anotación y Modossrc/main/java/org/tinystruct/application/ActionRegistry.java— Motor de Enrutamientosrc/main/java/org/tinystruct/data/component/Builder.java— Serializador de objetos JSONsrc/main/java/org/tinystruct/data/component/Builders.java— Serializador de arrays JSONsrc/main/java/org/tinystruct/data/component/AbstractData.java— Clase base POJO con CRUDsrc/main/java/org/tinystruct/data/Mapping.java— Analizador de XML de mapeosrc/main/java/org/tinystruct/data/tools/MySQLGenerator.java— Referencia del generador de POJOssrc/main/java/org/tinystruct/data/component/FieldType.java— Mapeos de tipos SQL a Javasrc/main/java/org/tinystruct/data/component/Condition.java— Constructor de consultas SQL fluidosrc/main/java/org/tinystruct/http/SSEPushManager.java— Gestión de conexiones SSEsrc/test/java/org/tinystruct/application/ActionRegistryTest.java— Ejemplos de prueba del registrosrc/test/java/org/tinystruct/system/HttpServerHttpModeTest.java— Patrones de prueba de integración HTTP