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

249 lines
7.5 KiB
Markdown

# Architecture
## Module Overview
```
clotho-core
Zero Spring dependency.
All mapping, session, registry, and query logic.
clotho-spring-boot-starter
Spring Boot 3.x autoconfiguration.
Thin layer: creates beans, bridges @Transactional, scans @ClothoRepository.
Depends on clotho-core.
clotho-dev-app
Minimal Spring Boot application.
Connected to a live ArcadeDB instance.
Used throughout development for real-DB testing.
Not a production artifact; not published.
```
---
## clotho-core Component Map
```
ClothoSessionFactory (entry point)
│ Holds: DriverRemoteConnection, TypeRegistry, global config
├── TypeRegistry
│ Built once at startup from classpath scan.
│ Maps: ArcadeDB label → Class<? extends ClothoEntity>
│ Knows: Java + ArcadeDB inheritance hierarchy, additionalParents
└── ClothoSession (unit of work, one per transaction/request)
│ Holds: identity map (ARID → ClothoEntity instance)
├── load(Class<T>, ARID) → T
│ └── SubgraphLoader
│ Uses: TypeRegistry, ObjectMapper
│ Executes Gremlin traversal for root vertex + @Include boundary
│ Cycle detection via visited-ARID set
├── save(entity)
│ └── SubgraphPersistor
│ Upserts root vertex/edge + all @Include elements recursively
│ Uses ArcadeDB @version for optimistic locking
│ @SubgraphType: iterates all ClothoEntity fields, saves each
├── delete(entity)
│ Removes root vertex/edge from graph
│ Does NOT cascade deletion to @Include elements
└── query(Class<T>, queryName, params) → List<T>
└── QueryExecutor
Resolves named @Query, binds params, executes, maps results
Also drives @ClothoRepository proxy methods
ObjectMapper (used by SubgraphLoader and QueryExecutor)
Gremlin Vertex/Edge → ClothoEntity subclass
Always uses most-specific registered class (TypeRegistry lookup by label)
Sets @Property fields, @OutVertex/@InVertex fields on edges
SubgraphTypeAssembler (used by QueryExecutor for @SubgraphType returns)
Maps Gremlin select() result map → @SubgraphType fields
Binding: field name or @Alias → select key
Each bound value is mapped via ObjectMapper
```
---
## Data Flow: Loading a Composite Object
```
Developer: session.load(Order.class, arid)
SubgraphLoader
1. g.V(arid.asString()) → TinkerPop Vertex (Order)
2. ObjectMapper.map(vertex) → Order instance (most-specific class)
3. Register in identity map
4. For each @Include field on Order:
@Include @Via("HAS_LINE") List<OrderLine> lines
┌──────────────────────────────────────────┐
│ g.V(arid).out("HAS_LINE") │
│ → [TinkerPop Vertex, Vertex, ...] │
│ for each: ObjectMapper.map(v) │
│ → OrderLine (or subtype by label) │
│ recurse: load OrderLine @Include fields│
└──────────────────────────────────────────┘
5. Assemble Order.lines = [OrderLine, ...]
6. Return fully assembled Order
```
---
## Data Flow: Saving a Composite Object
```
Developer: session.save(order)
SubgraphPersistor
1. Upsert root Order vertex
if order.getId() == null → create vertex with label "Order"
else → update vertex, check @version (optimistic lock)
2. For each @Include field on Order:
Field type is @VertexType (e.g., List<OrderLine>):
for each element → recursively save (SubgraphPersistor.save(orderLine))
ensure edge "HAS_LINE" exists from Order to OrderLine
Field type is @EdgeType (e.g., List<WorksAt>):
save edge properties; ensure @OutVertex and @InVertex are already persisted
3. Identity map deduplication: elements already saved in this call are skipped
4. Removed elements: NOT deleted automatically
```
---
## Data Flow: `@Query` on a `@ClothoRepository`
```
Developer: employeeQueries.findByDepartment("Engineering")
ClothoRepository proxy (generated by QueryExecutor at startup)
1. Read @Query gremlin string from method annotation
2. Bind @Param values: replace :dept with "Engineering"
3. Execute: g.V().hasLabel('Employee').has('dept','Engineering')
4. For each result vertex:
ObjectMapper.map(vertex) → most-specific Employee subclass
5. Return List<Employee>
```
For `@SubgraphType` return:
```
3. Execute query with select() → List of Maps
4. For each Map:
SubgraphTypeAssembler.assemble(EmployeeProfile.class, map)
→ bind map keys to fields by name / @Alias
→ ObjectMapper.map() each value
5. Return List<EmployeeProfile>
```
---
## Session Identity Map
The identity map is the session's in-memory cache of loaded vertices and edges.
- Key: `ARID`
- Value: `ClothoEntity` instance
Rules:
- First load of an ARID → fetch from DB, map to Java, store in map, return
- Subsequent load of same ARID → return existing instance (no DB call)
- `save()` does NOT clear the identity map; the instance is mutated in place
- `close()` clears the map and releases the Gremlin connection
The map operates at the individual vertex/edge level. Multiple composite objects (e.g., an
`Order` and an `EmployeeProfile`) may both hold references to the same `Address` instance
if their traversals reached the same ARID.
---
## TypeRegistry Startup Validation
At `ClothoSessionFactory.build()` time, the TypeRegistry validates:
- Every `@VertexType` class extends `ClothoVertex`
- Every `@EdgeType` class extends `ClothoEdge`
- No two classes claim the same label
- `additionalParents` labels are resolvable to registered classes
- `parentLabel` values are resolvable to registered classes
- Every `@Include` field is paired with `@Via`
- `@OutVertex` / `@InVertex` field types are `@VertexType` classes
Startup failure with a descriptive message is strongly preferred over silent misconfiguration
that produces confusing runtime errors.
---
## Package Structure
```
com.binarygolem.clotho
.model
ClothoEntity
ClothoVertex
ClothoEdge
ARID
UnknownTypeBehavior
.annotation
VertexType
EdgeType
SubgraphType
RID
Property
Include
Via
Direction
OutVertex
InVertex
Alias
Query
Queries (container for @Repeatable @Query)
Param
ClothoRepository
.session
ClothoSession
ClothoSessionFactory
ClothoSessionFactoryBuilder
.registry
TypeRegistry
TypeRegistryBuilder
TypeDescriptor (holds metadata for one registered type)
.mapping
ObjectMapper
SubgraphLoader
SubgraphPersistor
SubgraphTypeAssembler
.query
QueryExecutor
QueryDescriptor (holds parsed @Query metadata for one method)
ParameterBinder
.exception
ClothoException
UnknownTypeException
OptimisticLockException
ClothoConfigurationException
// clotho-spring-boot-starter
com.binarygolem.clotho.spring
.config
ClothoAutoConfiguration
ClothoProperties
.tx
ClothoTransactionManager
ClothoTransactionObject
.repository
ClothoRepositoryScanner
ClothoRepositoryProxyFactory
```