tinystruct 開発パターン
tinystruct Javaフレームワークを使用したモジュール構築のためのアーキテクチャと実装パターン。軽量で高性能なフレームワークであり、CLIとHTTPを同等の地位で扱い、main()メソッドや最小限の設定を必要としません。
核となる原則
CLIとHTTPは同等の市民です。 @Actionでアノテーションされたすべてのメソッドは、修正なしでターミナルとWebブラウザの両方から実行可能であるべきです。この「デュアルモード」機能は、tinystructの中心的な設計思想です。
アクティベーションのタイミング
使用すべきケース
AbstractApplicationを拡張して新しいApplicationモジュールを作成する場合。@Actionを使用してルートとコマンドラインアクションを定義する場合。Contextを介してリクエストごとの状態を処理する場合。- ネイティブの
BuilderおよびBuildersコンポーネントを使用してJSONシリアライゼーションを実行する場合。 AbstractDataPOJOを使用したデータベース永続化を扱う場合。generateコマンドを使用してデータベーステーブルからPOJOを生成する場合。- リアルタイムプッシュのためにServer-Sent Events (SSE)を実装する場合。
- マルチパートデータを使用したファイルアップロードを処理する場合。
URLRequestとHTTPHandlerを使用してアウトバウンドHTTPリクエストを行う場合。application.propertiesでデータベース接続やシステム設定を構成する場合。- ルーティングの競合(アクション)やCLI引数の解析をデバッグする場合。
仕組み
tinystructフレームワークは、@Actionでアノテーションされた任意のメソッドを、ターミナル環境とWeb環境の両方でルーティング可能なエンドポイントとして扱います。アプリケーションは、init()などのライフサイクルフックとリクエストContextへのアクセスを提供するAbstractApplicationを拡張して作成されます。
ルーティングはActionRegistryによって処理され、パスセグメントをメソッド引数に自動的にマッピングし、依存関係を注入します。データのみのサービスの場合、ゼロ依存フットプリントを維持するために、ネイティブのBuilderおよびBuildersコンポーネントをJSONシリアライゼーションに使用する必要があります。データベース層は、外部ORMライブラリなしでCRUD操作を行うために、XMLマッピングファイルと対になるAbstractData POJOを使用します。
例
基本的なアプリケーション (MyService)
public class MyService extends AbstractApplication {
@Override
public void init() {
this.setTemplateRequired(false); // データ/APIアプリでは.view検索を無効化
}
@Override public String version() { return "1.0.0"; }
@Action("greet")
public String greet() {
return "Hello from tinystruct!";
}
// パスパラメータ: GET /?q=greet/James または bin/dispatcher greet/James
@Action("greet")
public String greet(String name) {
return "Hello, " + name + "!";
}
}
HTTPモードの曖昧さ回避 (login)
@Action(value = "login", mode = Mode.HTTP_POST)
public String doLogin(Request<?, ?> request) throws ApplicationException {
request.getSession().setAttribute("userId", "42");
return "Logged in";
}
ネイティブ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\":\"Connected to SSE\"}";
}
// 特定のクライアントにプッシュ
String sessionId = getContext().getId();
Builder msg = new Builder();
msg.put("text", "Hello, user!");
SSEPushManager.getInstance().push(sessionId, msg);
// 全クライアントにブロードキャスト
SSEPushManager.getInstance().broadcast(msg);
ファイルアップロード
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("Uploaded: " + file.getFilename());
}
}
return "Upload OK";
}
MCPサーバーとツールの統合
tinystructは、SDKバージョン**1.7.26**以降、Model Context Protocol (MCP)のネイティブサポートを提供します。
MCP API(例:org.tinystruct.mcp.MCPTool、org.tinystruct.mcp.MCPServer、org.tinystruct.mcp.MCPException)は、コア依存関係に直接含まれています:
<dependency>
<groupId>org.tinystruct</groupId>
<artifactId>tinystruct</artifactId>
<version>1.7.26</version>
</dependency>
セキュリティ警告(プロンプトインジェクション): ツールの戻り値は、AIモデルのコンテキストウィンドウに直接フィードバックされます。ツールの戻り文字列に呼び出し元から提供された引数を含める前に、それらを必ず検証およびサニタイズする必要があります。入力をサニタイズしないと、攻撃者が敵対的な指示(プロンプトインジェクション)を挿入し、モデルの動作を上書きする可能性があります。長さ、文字セット、およびnull値を常に検証してください。
MCPツールを作成するには:
org.tinystruct.mcp.MCPToolを拡張します。@Actionで操作をアノテーションし、arguments配列内で@Argumentを使用してパラメーターを宣言します。@Argumentのキーと一致する明示的なメソッド引数としてパラメーターを受け入れます。(ツール引数にgetContext().getAttribute(...)を使用しないでください)。
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", "MCPをデモするカスタムツール");
}
@Action(
value = "custom/hello",
description = "誰かに挨拶する",
arguments = {
@Argument(key = "name", description = "挨拶する名前", type = "string", optional = false)
}
)
public String hello(String name) throws MCPException {
// セキュリティ:プロンプトインジェクションの脆弱性を防ぐため、
// モデルに返す前にツール入力を検証/サニタイズします。
if (name == null || name.length() > 50 || !name.matches("^[a-zA-Z0-9 ]+$")) {
throw new MCPException("無効な名前が指定されました");
}
return "Hello, " + name + "!";
}
}
MCPサーバーをデプロイするには:
org.tinystruct.mcp.MCPServerを拡張します。init()をオーバーライドし、this.registerTool()を使用してツールを登録します。フレームワークが@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";
}
}
ディスパッチャーを使用してサーバーを実行します:
bin/dispatcher start --import org.tinystruct.system.HttpServer --import com.example.MyMCPServer
設定
設定はsrc/main/resources/application.propertiesで管理されます。
# データベース
driver=org.h2.Driver
database.url=jdbc:h2:~/mydb
database.user=sa
database.password=
# サーバー
default.home.page=hello
server.port=8080
# ロケール
default.language=en_US
# セッション(クラスター環境ではRedis)
# default.session.repository=org.tinystruct.http.RedisSessionRepository
# redis.host=127.0.0.1
# redis.port=6379
アプリケーション内で設定値にアクセスする:
String port = this.getConfiguration("server.port");
レッドフラグとアンチパターン
| 症状 | 正しいパターン |
|---|---|
com.google.gsonまたはcom.fasterxml.jacksonをインポート | org.tinystruct.data.component.Builder / Buildersを使用。 |
JSON配列にList<Builder>を使用 | ジェネリック型消去の問題を避けるためにBuildersを使用。 |
ApplicationRuntimeException: template not found | API専用アプリの場合はinit()でsetTemplateRequired(false)を呼び出す。 |
privateメソッドに@Actionを付与 | フレームワークによって登録されるには、アクションはpublicである必要があります。 |
アプリ内でのmain(String[] args)のハードコーディング | すべてのモジュールのエントリポイントとしてbin/dispatcherを使用。 |
手動によるActionRegistry登録 | 自動検出には@Actionアノテーションを推奨。 |
| 実行時にアクションが見つからない | クラスが--importでインポートされているか、application.propertiesにリストされていることを確認。 |
| CLI引数が見えない | --key valueで渡し、getContext().getAttribute("--key")でアクセス。 |
| 同じパスの2つのメソッドで、間違った方が実行される | 曖昧さを回避するために明示的なmode(例:HTTP_GET vs HTTP_POST)を設定。 |
ベストプラクティス
- 粒度の細かいアプリケーション:ロジックを1つの巨大なクラスではなく、より小さく焦点を絞ったアプリケーションに分割します。
init()でのセットアップ:コンストラクタではなく、init()をセットアップ(設定、DB)に活用します。setAction()を呼び出さないでください —@Actionアノテーションを使用します。- モード認識:
@ActionのModeパラメータを使用して、機密性の高い操作をCLIのみまたは特定のHTTPメソッドに制限します。 - パラメータよりコンテキスト:オプションのCLIフラグの場合、メソッドシグネチャにパラメータを追加する代わりに、
getContext().getAttribute("--flag")を使用します。 - 非同期イベント:イベントによってトリガーされる重いタスクには、イベントハンドラー内で
CompletableFuture.runAsync()を使用します。
テクニカルリファレンス
詳細なガイドはreferences/ディレクトリにあります:
- アーキテクチャと設定 — 抽象化、パッケージマップ、プロパティ
- ルーティングと@Action — アノテーションの詳細、モード、パラメータ
- データ処理 — Builder、Builders、JSONシリアライゼーションとパース
- データベース永続化 — AbstractData POJO、CRUD、マッピングXML、POJO生成
- システムと使用法 — コンテキスト、セッション、SSE、ファイルアップロード、イベント、ネットワーキング
- テストパターン — JUnit 5のユニットテストとHTTP統合テスト
リファレンスソースファイル(内部)
src/main/java/org/tinystruct/AbstractApplication.java— ライフサイクルフックを含むコアベースクラスsrc/main/java/org/tinystruct/system/annotation/Action.java— アノテーションとモードsrc/main/java/org/tinystruct/application/ActionRegistry.java— ルーティングエンジンsrc/main/java/org/tinystruct/data/component/Builder.java— JSONオブジェクトシリアライザーsrc/main/java/org/tinystruct/data/component/Builders.java— JSON配列シリアライザーsrc/main/java/org/tinystruct/data/component/AbstractData.java— CRUDを含むベースPOJOクラスsrc/main/java/org/tinystruct/data/Mapping.java— マッピングXMLパーサーsrc/main/java/org/tinystruct/data/tools/MySQLGenerator.java— POJOジェネレーターのリファレンスsrc/main/java/org/tinystruct/data/component/FieldType.java— SQLからJavaへの型マッピングsrc/main/java/org/tinystruct/data/component/Condition.java— 流暢なSQLクエリビルダーsrc/main/java/org/tinystruct/http/SSEPushManager.java— SSE接続管理src/test/java/org/tinystruct/application/ActionRegistryTest.java— レジストリテストの例src/test/java/org/tinystruct/system/HttpServerHttpModeTest.java— HTTP統合テストのパターン