tinystruct-Entwicklungsmuster
Architektur- und Implementierungsmuster für die Erstellung von Modulen mit dem tinystruct Java-Framework – einem leichten, leistungsstarken Framework, das CLI und HTTP als gleichberechtigte Bürger behandelt und weder eine main()-Methode noch minimale Konfiguration erfordert.
Kernprinzip
CLI und HTTP sind gleichberechtigte Bürger. Jede mit @Action annotierte Methode sollte idealerweise sowohl über ein Terminal als auch über einen Webbrowser ohne Änderungen ausführbar sein. Diese „Dual-Mode“-Fähigkeit ist das zentrale Designprinzip von tinystruct.
Wann aktivieren
Verwendungszwecke
- Erstellen neuer
Application-Module durch Erweiterung vonAbstractApplication. - Definieren von Routen und Befehlszeilenaktionen mit
@Action. - Verwalten von anfragespezifischen Zuständen über
Context. - Durchführen von JSON-Serialisierung mit den nativen Komponenten
BuilderundBuilders. - Arbeiten mit Datenbankpersistenz über
AbstractData-POJOs. - Generieren von POJOs aus Datenbanktabellen mit dem Befehl
generate. - Implementieren von Server-Sent Events (SSE) für Echtzeit-Push-Benachrichtigungen.
- Behandeln von Dateiuploads über Multipart-Daten.
- Tätigen ausgehender HTTP-Anfragen mit
URLRequestundHTTPHandler. - Konfigurieren von Datenbankverbindungen oder Systemeinstellungen in
application.properties. - Debuggen von Routing-Konflikten (Actions) oder der CLI-Argument-Parsing.
Funktionsweise
Das tinystruct-Framework behandelt jede mit @Action annotierte Methode als routbaren Endpunkt sowohl für Terminal- als auch für Webumgebungen. Anwendungen werden durch Erweiterung von AbstractApplication erstellt, das Kern-Lebenszyklus-Hooks wie init() und Zugriff auf den Anfrage-Context bereitstellt.
Das Routing wird von der ActionRegistry übernommen, die automatisch Pfadsegmente auf Methodenargumente abbildet und Abhängigkeiten injiziert. Für reine Daten-Services sollten die nativen Komponenten Builder und Builders zur JSON-Serialisierung verwendet werden, um eine Null-Abhängigkeits-Footprint beizubehalten. Die Datenbankschicht verwendet AbstractData-POJOs in Verbindung mit XML-Mapping-Dateien für CRUD-Operationen ohne externe ORM-Bibliotheken.
Beispiele
Einfache Anwendung (MyService)
public class MyService extends AbstractApplication {
@Override
public void init() {
this.setTemplateRequired(false); // .view-Lookup für Daten-/API-Apps deaktivieren
}
@Override public String version() { return "1.0.0"; }
@Action("greet")
public String greet() {
return "Hallo von tinystruct!";
}
// Pfadparameter: GET /?q=greet/James ODER bin/dispatcher greet/James
@Action("greet")
public String greet(String name) {
return "Hallo, " + name + "!";
}
}
HTTP-Modus-Trennung (login)
@Action(value = "login", mode = Mode.HTTP_POST)
public String doLogin(Request<?, ?> request) throws ApplicationException {
request.getSession().setAttribute("userId", "42");
return "Eingeloggt";
}
Native JSON-Datenverarbeitung (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\":\"Mit SSE verbunden\"}";
}
// Push an einen bestimmten Client
String sessionId = getContext().getId();
Builder msg = new Builder();
msg.put("text", "Hallo, Benutzer!");
SSEPushManager.getInstance().push(sessionId, msg);
// Broadcast an alle
SSEPushManager.getInstance().broadcast(msg);
Dateiupload
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("Hochgeladen: " + file.getFilename());
}
}
return "Upload OK";
}
MCP-Server- und Tools-Integration
tinystruct bietet native Unterstützung für das Model Context Protocol (MCP) ab SDK-Version 1.7.26.
Die MCP-APIs (z. B. org.tinystruct.mcp.MCPTool, org.tinystruct.mcp.MCPServer, org.tinystruct.mcp.MCPException) sind direkt in der Kernabhängigkeit enthalten:
<dependency>
<groupId>org.tinystruct</groupId>
<artifactId>tinystruct</artifactId>
<version>1.7.26</version>
</dependency>
SICHERHEITSWARNUNG (Prompt Injection): Rückgabewerte von Tools werden direkt in den Kontext des KI-Modells eingespeist. Sie MÜSSEN alle vom Aufrufer bereitgestellten Argumente validieren und bereinigen, bevor Sie sie in die Rückgabezeichenfolge des Tools aufnehmen. Eine fehlende Bereinigung der Eingaben kann es einem Angreifer ermöglichen, adversariale Anweisungen (Prompt Injection) einzuschleusen, die das Verhalten des Modells überschreiben. Validieren Sie immer Länge, Zeichensatz und Nullwerte.
So erstellen Sie ein MCP-Tool:
- Erweitern Sie
org.tinystruct.mcp.MCPTool. - Annotieren Sie Operationen mit
@Actionund deklarieren Sie Parameter mit@Argumentimarguments-Array. - Übernehmen Sie Parameter als explizite Methodenargumente, die mit den Schlüsseln in
@Argumentübereinstimmen. (Verwenden Sie nichtgetContext().getAttribute(...)für Tool-Argumente).
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", "Ein benutzerdefiniertes Tool zur Demonstration von MCP");
}
@Action(
value = "custom/hello",
description = "Jemanden begrüßen",
arguments = {
@Argument(key = "name", description = "Der zu begrüßende Name", type = "string", optional = false)
}
)
public String hello(String name) throws MCPException {
// SICHERHEIT: Tool-Eingaben validieren/bereinigen, bevor sie an das Modell zurückgegeben werden,
// um Prompt-Injection-Schwachstellen zu vermeiden.
if (name == null || name.length() > 50 || !name.matches("^[a-zA-Z0-9 ]+$")) {
throw new MCPException("Ungültiger Name angegeben");
}
return "Hallo, " + name + "!";
}
}
So stellen Sie einen MCP-Server bereit:
- Erweitern Sie
org.tinystruct.mcp.MCPServer. - Überschreiben Sie
init()und registrieren Sie Ihre Tools mitthis.registerTool(). Das Framework scannt und mappt automatisch die@Action-Methoden.
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";
}
}
Starten Sie den Server über den Dispatcher:
bin/dispatcher start --import org.tinystruct.system.HttpServer --import com.example.MyMCPServer
Konfiguration
Einstellungen werden in src/main/resources/application.properties verwaltet.
# Datenbank
driver=org.h2.Driver
database.url=jdbc:h2:~/mydb
database.user=sa
database.password=
# Server
default.home.page=hello
server.port=8080
# Gebietsschema
default.language=en_US
# Sitzung (Redis für Cluster-Umgebungen)
# default.session.repository=org.tinystruct.http.RedisSessionRepository
# redis.host=127.0.0.1
# redis.port=6379
Greifen Sie in Ihrer Anwendung auf Konfigurationswerte zu:
String port = this.getConfiguration("server.port");
Warnsignale und Anti-Patterns
| Symptom | Korrektes Muster |
|---|---|
Import von com.google.gson oder com.fasterxml.jackson | Verwenden Sie org.tinystruct.data.component.Builder / Builders. |
Verwendung von List<Builder> für JSON-Arrays | Verwenden Sie Builders, um Probleme mit generischer Typ-Auslöschung zu vermeiden. |
ApplicationRuntimeException: template not found | Rufen Sie setTemplateRequired(false) in init() für reine API-Apps auf. |
Annotieren von private-Methoden mit @Action | Aktionen müssen public sein, um vom Framework registriert zu werden. |
Hartcodierung von main(String[] args) in Apps | Verwenden Sie bin/dispatcher als Einstiegspunkt für alle Module. |
Manuelle ActionRegistry-Registrierung | Bevorzugen Sie die @Action-Annotation zur automatischen Erkennung. |
| Aktion zur Laufzeit nicht gefunden | Stellen Sie sicher, dass die Klasse über --import importiert oder in application.properties gelistet ist. |
| CLI-Argument nicht sichtbar | Übergeben Sie es mit --key value; greifen Sie mit getContext().getAttribute("--key") darauf zu. |
| Zwei Methoden gleicher Pfad, falsche feuert | Legen Sie einen expliziten mode fest (z. B. HTTP_GET vs HTTP_POST), um zu trennen. |
Best Practices
- Granulare Anwendungen: Teilen Sie die Logik in kleinere, fokussierte Anwendungen statt in eine monolithische Klasse.
- Aufsetzen in
init(): Nutzen Sieinit()für die Einrichtung (Konfiguration, DB) anstelle des Konstruktors. Rufen Sie nichtsetAction()auf – verwenden Sie die@Action-Annotation. - Modusbewusstsein: Verwenden Sie den
Mode-Parameter in@Action, um sensible Operationen aufCLIoder bestimmte HTTP-Methoden zu beschränken. - Kontext statt Parameter: Verwenden Sie für optionale CLI-Flags
getContext().getAttribute("--flag")anstatt Parameter zur Methodensignatur hinzuzufügen. - Asynchrone Ereignisse: Verwenden Sie für schwere Aufgaben, die durch Ereignisse ausgelöst werden,
CompletableFuture.runAsync()im Ereignis-Handler.
Technische Referenz
Ausführliche Anleitungen sind im Verzeichnis references/ verfügbar:
- Architektur & Konfiguration — Abstraktionen, Paketübersicht, Eigenschaften
- Routing & @Action — Anmerkungsdetails, Modi, Parameter
- Datenverarbeitung — Builder, Builders, JSON-Serialisierung & -Parsing
- Datenbankpersistenz — AbstractData-POJOs, CRUD, Mapping-XML, POJO-Generierung
- System & Verwendung — Kontext, Sitzungen, SSE, Dateiuploads, Ereignisse, Netzwerk
- Testmuster — JUnit 5 Unit- und HTTP-Integrationstests
Referenz-Quelldateien (Intern)
src/main/java/org/tinystruct/AbstractApplication.java— Kern-Basisklasse mit Lebenszyklus-Hookssrc/main/java/org/tinystruct/system/annotation/Action.java— Annotation & Modisrc/main/java/org/tinystruct/application/ActionRegistry.java— Routing-Enginesrc/main/java/org/tinystruct/data/component/Builder.java— JSON-Objekt-Serialisierersrc/main/java/org/tinystruct/data/component/Builders.java— JSON-Array-Serialisierersrc/main/java/org/tinystruct/data/component/AbstractData.java— Basis-POJO-Klasse mit CRUDsrc/main/java/org/tinystruct/data/Mapping.java— Mapping-XML-Parsersrc/main/java/org/tinystruct/data/tools/MySQLGenerator.java— POJO-Generator-Referenzsrc/main/java/org/tinystruct/data/component/FieldType.java— SQL-zu-Java-Typzuordnungensrc/main/java/org/tinystruct/data/component/Condition.java— Fließender SQL-Abfrage-Buildersrc/main/java/org/tinystruct/http/SSEPushManager.java— SSE-Verbindungsverwaltungsrc/test/java/org/tinystruct/application/ActionRegistryTest.java— Registry-Testbeispielesrc/test/java/org/tinystruct/system/HttpServerHttpModeTest.java— HTTP-Integrationstestmuster