9.9 KiB
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.
@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.
@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.
@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:
@Includefields default toFetchType.EAGER(always loaded). Declare@Include(fetchType = FetchType.LAZY)to skip a field duringpopulate(). Lazy fields remainnull/empty until the developer callssession.loadField(vertex, "fieldName")explicitly. No proxy or bytecode enhancement is used. - Single-query boundary loading: All
@Includefields for a vertex are fetched in a single Gremlinproject()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:
ClothoEntitytracks which fields were mutated vianotifyFieldChanged()(called from manual setters). On save,NEWentities write all@Propertyfields;MANAGEDentities 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 versionon an entity field. Clotho reads ArcadeDB's built-in@versionproperty on load and includes a.has("@version", version)filter on every update. A version mismatch throwsOptimisticLockException. Both vertices and edges support@Version. - Edges are first-class citizens:
ClothoEdgeentities participate in the sameEntityStatelifecycle, dirty tracking,@VersionOCC, identity map caching, and auto-save-on-close asClothoVertexentities. Edges can be loaded by ID viasession.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.
additionalParentson@VertexTypehandles ArcadeDB's multi-parent (diamond) inheritance where Java's single inheritance is insufficient. - No automatic deletion: Removing an element from an
@Includecollection does not delete it from the graph. Callsession.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.
import org.jspecify.annotations.NonNull;
import org.jspecify.annotations.Nullable;
Lombok
Use Lombok where it reduces noise without obscuring intent:
@Getteron domain classes@Builderon factory/config classes@RequiredArgsConstructor/@AllArgsConstructoron service classes@Valueis 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:
// 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/@EdgeTypeannotation 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
mvn clean install
Run clotho-dev-app
mvn -pl clotho-dev-app spring-boot:run
Connection (local ArcadeDB default)
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<T> — 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 |