242 lines
9.9 KiB
Markdown
242 lines
9.9 KiB
Markdown
# 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<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 |
|