# Clotho A Java OGM (Object-Graph Mapper) for ArcadeDB, built on Apache TinkerPop/Gremlin. Clotho is not a 1:1 node-to-object mapper. A "Clotho object" is a subgraph — a root vertex plus a declared set of edges and adjacent vertices spanning any number of hops. Complex graph traversals and analytics remain the developer's responsibility, written via `@Query` on repository interfaces. Clotho handles bounded object loading and persistence. --- ## Module Structure ``` clotho/ clotho-core/ # Zero Spring dependency. All mapping logic. clotho-spring-boot-starter/ # Autoconfigure, @Transactional bridge, Spring beans. clotho-dev-app/ # Minimal Spring Boot app for development testing against # a live ArcadeDB instance. Not a production artifact. ``` --- ## Three Tiers of Graph-to-Java Mapping ### 1. Vertex Type A single ArcadeDB vertex mapped to a Java class. Must extend `ClothoVertex`. ```java @VertexType("Employee") public class Employee extends ClothoVertex { @Property("name") String name; @Include @Via("HAS_ADDRESS") Address address; // eagerly loaded as part of this object's boundary } ``` ### 2. Edge Type A single ArcadeDB edge mapped to a Java class. Must extend `ClothoEdge`. ```java @EdgeType("WORKS_AT") public class WorksAt extends ClothoEdge { @OutVertex Employee employee; @InVertex Company company; // omit @InVertex for edge-only inclusion LocalDate startDate; } ``` ### 3. Subgraph Type A composite Java class assembled from multiple vertices and edges via a `@Query` traversal. Has no ARID. Does NOT extend `ClothoVertex` or `ClothoEdge`. `save()` delegates to all component `ClothoEntity` fields individually. ```java @SubgraphType public class EmployeeProfile { Employee employee; // field name matches Gremlin as("employee") Manager manager; @Alias("dept") Department department; } ``` --- ## Technology Stack | Concern | Library | |---|---| | Language | Java 21 | | Build | Maven (multi-module) | | Graph connection | Apache TinkerPop 3.x — `DriverRemoteConnection` over WebSocket | | Null safety | JSpecify (`org.jspecify.annotations`) | | Boilerplate reduction | Lombok | | Testing | JUnit 5 | | Spring integration | Spring Boot 3.x autoconfigure | --- ## Key Design Decisions - **Schema-first**: Clotho assumes the ArcadeDB schema already exists. No DDL generation. The developer manages the schema (e.g., using a Flyway-based migration tool). - **Optional lazy loading**: `@Include` fields default to `FetchType.EAGER` (always loaded). Declare `@Include(fetchType = FetchType.LAZY)` to skip a field during `populate()`. Lazy fields remain `null`/empty until the developer calls `session.loadField(vertex, "fieldName")` explicitly. No proxy or bytecode enhancement is used. - **Single-query boundary loading**: All `@Include` fields for a vertex are fetched in a single Gremlin `project()` traversal. This eliminates N+1 queries per object boundary and prevents race conditions from sequential round-trips. For nested boundaries, each vertex gets its own single-project query (one query per vertex, not one per field). - **Dirty tracking**: `ClothoEntity` tracks which fields were mutated via `notifyFieldChanged()` (called from manual setters). On save, `NEW` entities write all `@Property` fields; `MANAGED` entities write only dirty fields and skip the update entirely if nothing changed. `ClothoSession.close()` auto-saves all MANAGED entities (vertices and edges) that carry dirty fields. - **Optimistic locking**: Declare `@Version Long version` on an entity field. Clotho reads ArcadeDB's built-in `@version` property on load and includes a `.has("@version", version)` filter on every update. A version mismatch throws `OptimisticLockException`. Both vertices and edges support `@Version`. - **Edges are first-class citizens**: `ClothoEdge` entities participate in the same `EntityState` lifecycle, dirty tracking, `@Version` OCC, identity map caching, and auto-save-on-close as `ClothoVertex` entities. Edges can be loaded by ID via `session.load(EdgeType.class, id)`. - **Result mapping**: Clotho always instantiates the most specific registered class matching the vertex/edge label. Declared return types are upper bounds only. - **Inheritance**: Java class hierarchy is the primary mechanism. `additionalParents` on `@VertexType` handles ArcadeDB's multi-parent (diamond) inheritance where Java's single inheritance is insufficient. - **No automatic deletion**: Removing an element from an `@Include` collection does not delete it from the graph. Call `session.delete()` explicitly. See `docs/` for detailed specifications. --- ## Package Structure ``` com.binarygolem.clotho .core .model # ClothoEntity, ClothoVertex, ClothoEdge, ARID .annotation # All annotations (@VertexType, @EdgeType, @SubgraphType, etc.) .session # ClothoSession, ClothoSessionFactory .registry # TypeRegistry .mapping # ObjectMapper, SubgraphLoader, SubgraphPersistor, SubgraphTypeAssembler .query # QueryExecutor, @Query proxy infrastructure .exception # UnknownTypeException, ClothoException hierarchy .spring .config # ClothoAutoConfiguration .tx # Transaction bridge ``` --- ## Coding Conventions ### Nullability Every parameter, return type, and field must carry `@NonNull` or `@Nullable` from JSpecify. Default assumption is `@NonNull`; annotate only what can be null. ```java import org.jspecify.annotations.NonNull; import org.jspecify.annotations.Nullable; ``` ### Lombok Use Lombok where it reduces noise without obscuring intent: - `@Getter` on domain classes - `@Builder` on factory/config classes - `@RequiredArgsConstructor` / `@AllArgsConstructor` on service classes - `@Value` is NOT compatible with Clotho-managed domain objects (requires mutable fields) **`@Setter` restriction on entity classes:** Do NOT use Lombok `@Setter` on any field annotated with `@Property`, `@Include`, `@OutVertex`, `@InVertex`, or `@Version` on a `ClothoVertex` or `ClothoEdge` subclass. Write a manual setter that calls `notifyFieldChanged(fieldName)` so the session can track the change: ```java // Correct public void setName(String name) { this.name = name; notifyFieldChanged("name"); } // Wrong — dirty tracking does not fire @Setter String name; ``` Lombok `@Setter` remains allowed only for framework-managed fields (`id`, `outVertexId`, `inVertexId`) because Clotho sets those itself and they are not part of dirty tracking. ### Comments Write no comments unless the WHY is non-obvious. One line maximum. Never write doc comments that restate what the method name already says. ### Naming - ArcadeDB type names (labels) use the `@VertexType` / `@EdgeType` annotation value. Class names need not match label names exactly. - Edge label constants: define as `public static final String LABEL = "WORKS_AT"` on the edge class to avoid magic strings at call sites. --- ## Development Setup ### Prerequisites - Java 21 - Maven 3.9+ - A running ArcadeDB instance (for integration tests and `clotho-dev-app`) ### Build ```bash mvn clean install ``` ### Run `clotho-dev-app` ```bash mvn -pl clotho-dev-app spring-boot:run ``` ### Connection (local ArcadeDB default) ```properties clotho.gremlin.url=ws://localhost:8182/gremlin clotho.gremlin.username=root clotho.gremlin.password=playwithdata clotho.gremlin.traversal-source=g ``` --- ## Implementation Status | Phase | Description | Status | |---|---|---| | 1 | Project housekeeping — parent POM, module scaffold, package rename | Done | | 2 | Base types and annotations — `ARID`, `ClothoEntity`, `ClothoVertex`, `ClothoEdge`, all annotations, exception hierarchy | Done | | 3 | `clotho-dev-app` — Gremlin connection wiring, smoke test against live ArcadeDB | Done | | 4 | `TypeRegistry` — classpath scan, label→class map, hierarchy inference, `additionalParents`/`parentLabel` | Done | | 5 | `ElementMapper` — Gremlin `Vertex`/`Edge` → Java; always most-specific class by label | Done | | 6 | `SubgraphLoader` — traverse `@Include`/`@Via`, multi-hop, cycle detection, edge-only; `SubgraphTypeAssembler` | Done | | 7 | `SubgraphPersistor` — upsert, `@version` optimistic locking, cascade through boundary | Done | | 8 | `ClothoSession` / `ClothoSessionFactory` — identity map, `load`/`save`/`delete`/`query`, connection lifecycle | Done | | 9 | `ClothoRepository` — abstract base class; `g()`, `execute()`, `findAll()`, `findById()`, etc.; dropped `@Query`/`@Param` | Done | | 10 | Spring Boot Starter — `ClothoAutoConfiguration`, `ClothoProperties`, `@ClothoRepository` bean scanning | Done | | 11 | Dirty tracking (`notifyFieldChanged`, `EntityState`), `SessionContext`, single-query `project()` loading, `@Version` OCC, `FetchType.LAZY`, `FieldMetadataCache`, expanded type coercion, `populateAll()` batch loading, auto-save on close, edge first-class parity | Done | | 12 | Transaction management — `openTransactionalSession()`, borrow counter, `rollback()`, `getCurrentSession()`, `ClothoTransactionManager`, Spring `@Transactional` bridge, `ClothoAutoConfiguration` tx bean | Done | --- ## Documentation Index | Document | Contents | |---|---| | `docs/TYPE_SYSTEM.md` | `ClothoEntity` hierarchy, `@VertexType`, `@EdgeType`, `@SubgraphType`, inheritance, diamond inheritance, result mapping rule | | `docs/OBJECT_BOUNDARY.md` | `@Include`/`@Via`, multi-hop traversal, edge-only inclusion, cycle detection | | `docs/ANNOTATIONS.md` | Complete annotation reference with all attributes and examples | | `docs/QUERY_MODEL.md` | `@Query`, `@ClothoRepository`, `@SubgraphType` assembly, result mapping, parameter binding | | `docs/SPRING_INTEGRATION.md` | Autoconfigure, `@Transactional`, `application.properties` reference | | `docs/ARCHITECTURE.md` | Module breakdown, key components, data flow, session lifecycle | | `docs/TRANSACTIONS.md` | Transactional sessions, Spring `@Transactional`, nested propagation, `@Version` interaction, limitations |