Patrones de Desarrollo con tinystruct
Arquitectura y patrones de implementación para construir módulos con el framework Java tinystruct – un framework ligero y de alto rendimiento que trata a CLI y HTTP como ciudadanos de igual categoría, sin requerir un método main() y con una configuración mínima.
Principio Fundamental
CLI y HTTP son ciudadanos de igual categoría. Todo método anotado con @Action debería, idealmente, poder ejecutarse tanto desde una 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 Activar
Cuándo Usar
- 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 base 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 subidas de archivos mediante datos multipart.
- 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 terminal como para web. Las aplicaciones se crean extendiendo AbstractApplication, que proporciona hooks de ciclo de vida principales como init() y acceso al Context de la solicitud.
El enrutamiento lo gestiona ActionRegistry, que automáticamente asigna 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("greet")
public String greet() {
return "¡Hola desde tinystruct!";
}
// Parámetro de ruta: GET /?q=greet/James O bin/dispatcher greet/James
@Action("greet")
public String greet(String name) {
return "¡Hola, " + name + "!";
}
}
Desambiguación de Modo HTTP (login)
@Action(value = "login", mode = Mode.HTTP_POST)
public String doLogin(Request<?, ?> request) throws ApplicationException {
request.getSession().setAttribute("userId", "42");
return "Sesión iniciada";
}
Manejo Nativo de Datos JSON (Builder + Builders)
import org.tinystruct.data.component.Builder;
import org.tinystruct.data.component.Builders;
@Action("api/data")
public String getData() throws ApplicationException {
Builders dataList = new Builders();
Builder item = new Builder();
item.put("id", 1);
item.put("name", "James");
dataList.add(item);
Builder response = new Builder();
response.put("status", "success");
response.put("data", dataList);
return response.toString(); // {"status":"success","data":[{"id":1,"name":"James"}]}
}
SSE (Server-Sent Events)
import org.tinystruct.http.SSEPushManager;
@Action("sse/connect")
public String connect() {
return "{\"type\":\"connect\",\"message\":\"Conectado a SSE\"}";
}
// Enviar a un cliente específico
String sessionId = getContext().getId();
Builder msg = new Builder();
msg.put("text", "¡Hola, usuario!");
SSEPushManager.getInstance().push(sessionId, msg);
// Transmitir a todos
SSEPushManager.getInstance().broadcast(msg);
Subida de Archivos
import org.tinystruct.data.FileEntity;
@Action(value = "upload", mode = Mode.HTTP_POST)
public String upload(Request<?, ?> request) throws ApplicationException {
List<FileEntity> files = request.getAttachments();
if (files != null) {
for (FileEntity file : files) {
System.out.println("Subido: " + file.getFilename());
}
}
return "Subida OK";
}
Integración con Servidor MCP y Herramientas
tinystruct proporciona soporte nativo para el Protocolo de Contexto de Modelo (MCP) a partir de la versión 1.7.26 del SDK.
Las APIs MCP (por ejemplo, org.tinystruct.mcp.MCPTool, org.tinystruct.mcp.MCPServer, org.tinystruct.mcp.MCPException) se incluyen 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 introducen directamente en el 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 adversariales (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 arregloarguments. - Acepta los parámetros como argumentos explícitos del método que coincidan con las claves de
@Argument. (No usesgetContext().getAttribute(...)para los argumentos de la herramienta).
import org.tinystruct.mcp.MCPTool;
import org.tinystruct.mcp.MCPException;
import org.tinystruct.system.annotation.Action;
import org.tinystruct.system.annotation.Argument;
public class MyCustomTool extends MCPTool {
public MyCustomTool() {
super("custom", "Una herramienta personalizada para demostrar MCP");
}
@Action(
value = "custom/hello",
description = "Saludar a alguien",
arguments = {
@Argument(key = "name", description = "El nombre a saludar", type = "string", optional = false)
}
)
public String hello(String name) throws MCPException {
// SEGURIDAD: Validar/sanitizar las entradas de la herramienta antes de devolverlas al modelo
// para prevenir vulnerabilidades de inyección de prompts.
if (name == null || name.length() > 50 || !name.matches("^[a-zA-Z0-9 ]+$")) {
throw new MCPException("Nombre inválido proporcionado");
}
return "¡Hola, " + name + "!";
}
}
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 MyMCPServer extends MCPServer {
@Override
public void init() {
super.init();
this.registerTool(new MyCustomTool());
}
@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.example.MyMCPServer
Configuración
Los ajustes se gestionan en src/main/resources/application.properties.
# Base de datos
driver=org.h2.Driver
database.url=jdbc:h2:~/mydb
database.user=sa
database.password=
# Servidor
default.home.page=hello
server.port=8080
# Localización
default.language=en_US
# 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 port = 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 arreglos JSON | Usar Builders para evitar problemas de borrado de tipos genéricos. |
ApplicationRuntimeException: template not found | Llamar a setTemplateRequired(false) en init() para aplicaciones solo de API. |
Anotar métodos private con @Action | Las acciones deben ser public para que el framework las registre. |
Usar main(String[] args) directamente en aplicaciones | Usar bin/dispatcher como punto de entrada para todos los módulos. |
Registro manual de ActionRegistry | Preferir la anotación @Action para el descubrimiento automático. |
| Acción no encontrada en tiempo de ejecución | Asegurar que la clase se importe mediante --import o esté listada en application.properties. |
| Argumento CLI no visible | Pasar con --key value; acceder mediante getContext().getAttribute("--key"). |
| Dos métodos misma ruta, se ejecuta el equivocado | Establecer el mode explícito (ej., HTTP_GET vs HTTP_POST) para desambiguar. |
Buenas Prácticas
- Aplicaciones Granulares: Divide la lógica en aplicaciones más pequeñas y enfocadas en lugar de una sola 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("--flag")en lugar de agregar 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
Guías detalladas disponibles en el directorio references/:
- Arquitectura y Configuración — Abstracciones, Mapa de Paquetes, Propiedades
- Enrutamiento y @Action — Detalles de 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, Subida de Archivos, Eventos, Redes
- Patrones de Prueba — Pruebas unitarias con JUnit 5 y de integración HTTP
Archivos Fuente 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 arreglos JSONsrc/main/java/org/tinystruct/data/component/AbstractData.java— Clase base POJO con CRUDsrc/main/java/org/tinystruct/data/Mapping.java— Analizador XML de mapeosrc/main/java/org/tinystruct/data/tools/MySQLGenerator.java— Referencia de generador de POJOssrc/main/java/org/tinystruct/data/component/FieldType.java— Mapeos de SQL a tipos Javasrc/main/java/org/tinystruct/data/component/Condition.java— Constructor fluido de consultas SQLsrc/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