feat(core): add entity graph endpoint and service#56
Conversation
add include data parameter for showing properties in node
add include flag for getting properties or not in graph repository call
|
| GitGuardian id | GitGuardian status | Secret | Commit | Filename | |
|---|---|---|---|---|---|
| 30005138 | Triggered | Generic Password | ce8afc9 | src/test/java/com/decathlon/idp_core/AbstractIntegrationTest.java | View secret |
🛠 Guidelines to remediate hardcoded secrets
- Understand the implications of revoking this secret by investigating where it is used in your code.
- Replace and store your secret safely. Learn here the best practices.
- Revoke and rotate this secret.
- If possible, rewrite git history. Rewriting git history is not a trivial act. You might completely break other contributing developers' workflow and you risk accidentally deleting legitimate data.
To avoid such incidents in the future consider
- following these best practices for managing and storing secrets including API keys and other credentials
- install secret detection on pre-commit to catch secret before it leaves your machine and ease remediation.
🦉 GitGuardian detects secrets in your source code to help developers and security teams secure the modern development process. You are seeing this because you or someone else with access to this repository has authorized GitGuardian to scan your pull request.
There was a problem hiding this comment.
Pull request overview
This PR introduces an entity relationship graph capability to idp-core, adding domain modeling + persistence traversal support and exposing a new REST endpoint that returns a flat nodes/edges graph suitable for UI visualization.
Changes:
- Adds a new
GET /api/v1/entities/{templateIdentifier}/{entityIdentifier}/graphendpoint returning a flat graph DTO (nodes + edges), with optional depth / relation-name / property-name filters. - Introduces new domain graph models and a domain service to build depth-limited graphs (including inbound + outbound relations) and applies relation/property filtering.
- Adds a dedicated persistence adapter and repository queries (recursive CTE + batch fetch) plus expanded SQL seed data and both unit + integration tests.
Reviewed changes
Copilot reviewed 23 out of 33 changed files in this pull request and generated 14 comments.
Show a summary per file
| File | Description |
|---|---|
| src/test/resources/db/test/R__2_Insert_entities_test_data.sql | Extends seeded entities/relations/properties for query-filter and graph endpoint tests. |
| src/test/resources/db/test/R__1_Insert_test_data.sql | Ensures repeatable migrations clear child/parent tables in FK-safe order. |
| src/test/java/com/decathlon/idp_core/infrastructure/adapters/api/controller/EntityGraphControllerTest.java | New integration coverage for graph endpoint (depth, relation filter, property filter, auth/error cases). |
| src/test/java/com/decathlon/idp_core/infrastructure/adapters/api/controller/EntityControllerTest.java | Updates expected entity counts due to additional seeded entities. |
| src/test/java/com/decathlon/idp_core/domain/service/entity_graph/EntityGraphServiceTest.java | New unit coverage for depth clamping, filtering, inbound/outbound relations, and cycle guard behavior. |
| src/main/java/com/decathlon/idp_core/infrastructure/adapters/persistence/repository/JpaRelationRepository.java | Adjusts JPQL projection to return a typed summary record. |
| src/main/java/com/decathlon/idp_core/infrastructure/adapters/persistence/repository/JpaEntityRepository.java | Adds recursive CTE queries + batch-fetch helpers used for graph traversal. |
| src/main/java/com/decathlon/idp_core/infrastructure/adapters/persistence/PostgresEntityGraphAdapter.java | New persistence adapter implementing the graph repository port using CTE + batch loading. |
| src/main/java/com/decathlon/idp_core/infrastructure/adapters/api/mapper/entity/EntityGraphFlatDtoOutMapper.java | Flattens the recursive domain graph into nodes/edges DTOs with de-duplication guards. |
| src/main/java/com/decathlon/idp_core/infrastructure/adapters/api/dto/out/entity/RelationAsTargetSummaryDtoOut.java | New DTO for inbound relation summaries. |
| src/main/java/com/decathlon/idp_core/infrastructure/adapters/api/dto/out/entity/EntityGraphNodeFlatDtoOut.java | New flat node DTO (optional data map for properties). |
| src/main/java/com/decathlon/idp_core/infrastructure/adapters/api/dto/out/entity/EntityGraphFlatDtoOut.java | New top-level flat graph DTO (nodes + edges). |
| src/main/java/com/decathlon/idp_core/infrastructure/adapters/api/dto/out/entity/EntityGraphEdgeDtoOut.java | New edge DTO representing directed relations. |
| src/main/java/com/decathlon/idp_core/infrastructure/adapters/api/controller/EntityGraphController.java | New REST controller exposing the graph endpoint and query params. |
| src/main/java/com/decathlon/idp_core/infrastructure/adapters/api/configuration/SwaggerDescription.java | Adds OpenAPI description constants for the graph endpoint and DTO fields. |
| src/main/java/com/decathlon/idp_core/domain/service/entity_graph/EntityGraphService.java | New domain service assembling a depth-limited graph with filtering and cycle guards. |
| src/main/java/com/decathlon/idp_core/domain/port/EntityGraphRepositoryPort.java | New driven port for graph retrieval from persistence. |
| src/main/java/com/decathlon/idp_core/domain/model/entity/Relation.java | Adds explicit imports referenced by documentation/comments. |
| src/main/java/com/decathlon/idp_core/domain/model/entity/Property.java | Adds explicit imports referenced by documentation/comments. |
| src/main/java/com/decathlon/idp_core/domain/model/entity/EntityCompositeKey.java | New composite identifier record (template + identifier). |
| src/main/java/com/decathlon/idp_core/domain/model/entity/Entity.java | Adds explicit import referenced by documentation/comments. |
| src/main/java/com/decathlon/idp_core/domain/model/entity_graph/EntityGraphRelation.java | New domain model for graph relations (name + target nodes). |
| src/main/java/com/decathlon/idp_core/domain/model/entity_graph/EntityGraphNode.java | New domain model for graph nodes (properties + in/out relations). |
| src/main/java/com/decathlon/idp_core/domain/exception/entity_template/RelationNameAlreadyExistsException.java | Adds explicit import referenced by documentation/comments. |
| src/main/java/com/decathlon/idp_core/domain/exception/entity_template/PropertyNameAlreadyExistsException.java | Adds explicit import referenced by documentation/comments. |
| src/main/java/com/decathlon/idp_core/domain/exception/entity_template/EntityTemplateIdentifierCannotChangeException.java | Updates imports (but currently introduces an invalid Domain→Infrastructure dependency). |
| .pre-commit-config.yaml | Small formatting tweaks to hook comment spacing. |
| .github/instructions/domain.instructions.md | Expands documented domain exception/validation patterns and package organization guidance. |
| JOIN relation r ON r.id = er.relation_id | ||
| JOIN relation_target_entities rte ON rte.relation_id = r.id | ||
| JOIN entity e2 ON e2.identifier = rte.target_entity_identifier | ||
| WHERE og.depth < :depth |
| JOIN relation_target_entities rte ON rte.target_entity_identifier = e.identifier | ||
| JOIN relation r ON r.id = rte.relation_id | ||
| JOIN entity_relations er ON er.relation_id = r.id | ||
| JOIN entity e2 ON e2.id = er.entity_id | ||
| WHERE ig.depth < :depth |
| JOIN entity_relations er ON er.entity_id = e.id | ||
| JOIN relation r ON r.id = er.relation_id | ||
| JOIN relation_target_entities rte ON rte.relation_id = r.id | ||
| JOIN entity e2 ON e2.identifier = rte.target_entity_identifier | ||
| WHERE og.depth < :depth |
| JOIN relation_target_entities rte ON rte.target_entity_identifier = e.identifier | ||
| JOIN relation r ON r.id = rte.relation_id | ||
| JOIN entity_relations er ON er.relation_id = r.id | ||
| JOIN entity e2 ON e2.id = er.entity_id | ||
| WHERE ig.depth < :depth |
| @Query("SELECT DISTINCT e FROM EntityJpaEntity e LEFT JOIN FETCH e.relations WHERE e.identifier IN :identifiers") | ||
| List<EntityJpaEntity> findAllByIdentifierInWithRelations( | ||
| @Param("identifiers") Collection<String> identifiers); | ||
|
|
||
| /// Fetch properties for entities that were already loaded. This is called after | ||
| /// findAllByIdentifierInWithRelations to complete the entity graph. | ||
| @Query("SELECT DISTINCT e FROM EntityJpaEntity e LEFT JOIN FETCH e.properties WHERE e.identifier IN :identifiers") | ||
| List<EntityJpaEntity> findAllByIdentifierInWithProperties( | ||
| @Param("identifiers") Collection<String> identifiers); |
| /// @param relationNames when non-empty, only edges whose relation name is in | ||
| /// this set are | ||
| /// traversed; when empty, all relation types are followed |
| /// @param name the relation name as defined in the entity template | ||
| /// @param targetTemplateIdentifier the template identifier of the target entities | ||
| /// @param targets the resolved target entity graph nodes (recursively populated up to depth) |
| public static EntityCompositeKey fromString(String compositeKey) { | ||
| String[] parts = compositeKey.split(":", 2); | ||
| if (parts.length != 2) { | ||
| throw new IllegalArgumentException("Invalid composite key format: " + compositeKey); | ||
| } | ||
| return new EntityCompositeKey(parts[0], parts[1]); |
| @RestController | ||
| @RequestMapping("/api/v1/entities") | ||
| @RequiredArgsConstructor | ||
| @Tag(name = "Entity Graph", description = "Entity relationship graph operations") |
| -- Add to end of R__1_Insert_test_data.sql | ||
|
|
|



PR Description
What this PR Provides
This pull request introduces add new domain models and ports for entity graph traversal. Additionally, the package structure is refined to organize exceptions and services by subdomain.
Entity Graph Modeling
EntityGraphNodeandEntityGraphRelation, supporting hierarchical visualization and traversal of entity relationships.EntityGraphRepositoryPort, defines the contract for retrieving entity relationship graphs with support for depth, property inclusion, and efficient lookups.Domain Model Enhancements
EntityCompositeKeyrecord is added to uniquely identify entities across templates, supporting composite key parsing, equality, and string representation.Exception Handling and Validation Patterns
IllegalArgumentException), and enforce descriptive naming and contextual messages. A validation service pattern is introduced for reusable, fail-fast validation logic, with concrete code examples and naming conventions.Code Consistency
These changes collectively improve the robustness, clarity, and scalability of the domain layer.
Fixes
Review
The reviewer must double-check these points:
!after the type/scope to identify the breakingchange in the release note and ensure we will release a major version.
How to test
Test Scenarios
Graph API Test Scenarios - Generic Guide
Overview
This guide explains how to test the entity graph feature without assuming any pre-existing data. You'll need to set up a simple 3-node graph structure to validate all filtering capabilities.
Graph Structure Required
To test all scenarios, you need a graph with the following characteristics:
Entities
Create 3 entities
Properties
Each entity should have 2 properties to test property filtering:
tier,environment,status)version,owner,region)Use different values for each entity so you can verify property data is returned correctly.
Relations
Create 2 types of relations to test relation filtering:
Graph topology:
Why this structure?
Test Scenarios
✅ Scenario 1: Full Graph (No Filters)
Goal: Return all nodes and all edges
Request:
Expected Behavior:
include_data=false)What to verify:
source, target, andtype✅ Scenario 2: Filter by Relation Type 1
Goal: Only traverse edges of Relation-Type-1
Request:
Expected Behavior:
What to verify:
type: relation-type-2✅ Scenario 3: Filter by Relation Type 2
Goal: Only traverse edges of Relation-Type-2
Request:
Expected Behavior:
What to verify:
✅ Scenario 4: Include All Properties
Goal: Return property data for all nodes
Request:
Expected Behavior:
dataobject containing both property-1 and property-2What to verify:
datafielddata.property-1exists with correct valuedata.property-2exists with correct valueExample node structure:
{ "id": "template:entityA", "templateIdentifier": "template", "identifier": "entityA", "name": "Entity A Name", "data": { "property-1": "value-a-1", "property-2": "value-a-2" } }✅ Scenario 5: Filter Property 1 Only
Goal: Return only property-1 in node data
Request:
Expected Behavior:
dataobject contains only property-1What to verify:
data.property-1existsdata.property-2does not exist (notnull, completely absent)✅ Scenario 6: Filter Multiple Properties
Goal: Return both property-1 and property-2
Request:
Expected Behavior:
datacontains both propertiesWhat to verify:
data.property-1anddata.property-2exist✅ Scenario 7: Non-Existent Property Filter
Goal: Request a property that doesn't exist on any entity
Request:
Expected Behavior:
datafieldid,templateIdentifier,identifier,namedatafield is completely omitted (notnull, not{})What to verify:
datafield is absent from all nodes✅ Scenario 8: Combine Relation + Property Filters
Goal: Apply both relation and property filters simultaneously
Request:
Expected Behavior:
datawith only property-1What to verify:
data.property-1existsdata.property-2does not exist❌ Scenario 9: Entity Not Found
Goal: Request a non-existent entity identifier
Request:
Expected Behavior:
What to verify:
"Entity with template 'template-name' and identifier 'non-existent-entity' not found"❌ Scenario 10: Template Not Found
Goal: Request an entity from a non-existent template
Request:
Expected Behavior:
What to verify:
"Entity template with identifier 'non-existent-template' not found"Critical test: Scenario 3 (filter by type-2) must return only 2 nodes. If it returns 3 nodes, the filtering is not working correctly.
Breaking changes (if any)