249 lines
7.5 KiB
Markdown
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
|
|
```
|