clotho/CLAUDE.md
2026-05-15 00:09:01 +02:00

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 |