Bluetape4k Graph Part 4: Workshop Scenarios and Service Integration

This part maps the working bluetape4k-workshop graph examples. It is an overview, not a deep guide. The
goal is to help you recognize whether your own service has the same shape of problem. If an article only lists example
names and sends the reader back to the source tree, that is not documentation; it is homework distribution. So each
example below explains the relationship model and the decision it helps validate.
| Example | Graph pattern | Takeaway |
|---|---|---|
| Abuser detection | User-to-identifier identity graph | Shared device/IP/phone/payment/referral traversal |
| Recommendation | Purchase graph and follow graph | Co-buyer and friends-of-friends candidates |
| Knowledge graph | Document, entity, concept relationships | Semantic relationships and hop traversal |
| Social network | Person/company and multi-hop relationships | FOAF, mutual connections, shortest paths |
Abuser detection
Section titled “Abuser detection”Abuser detection is one of the most natural fits for a graph. Start from one user, follow identifiers such as device, IP address, phone hash, payment token, or referral, then find other users sharing those identifiers. You can build this with relational joins, but the query grows awkward as identifier types multiply.

AbuserDetectionService.findAbuseCluster() starts from a seed user, follows outgoing identifier edges, then traverses
back from those identifiers to related users. It excludes the seed user from the result. explainSuspicion() collects
edge paths for evidence, detectReferralLoops() uses cycle detection, and rankSuspiciousUsers() uses PageRank for the
review queue. Phone values should be hashes, and payment identifiers should be PCI-safe tokens.

Recommendation
Section titled “Recommendation”The recommendation example covers both product recommendations and follow recommendations.

RecommendationService.recommendProducts() finds products bought by the seed user, finds co-buyers for those products,
then recommends other products bought by those co-buyers. Already purchased products are excluded, and candidates are
scored by distinct co-buyer count. recommendFollows() creates 2-hop follow candidates, excludes the seed user and
existing follows, then sorts by mutual follow count. This is not a complete recommendation engine. It is a small skeleton
for separating candidate generation from scoring. For large graphs, replace this explicit traversal shape with native
Cypher or Gremlin queries. The common API makes the starting point and tests simpler; it should not wrap a heavy query
until the bottleneck looks prettier.

PURCHASED builds product candidates; FOLLOWS builds follow candidates. Candidate generation and scoring stay separate.Knowledge graph
Section titled “Knowledge graph”The knowledge graph example models Document, Entity, and Concept vertices. MENTIONS connects documents to
entities, RELATED_TO connects entities to other entities, and IS_A connects entities to concepts. The useful service
operations go beyond “search”: findMentionedEntities(documentId), findRelatedEntities(entityId, depth),
findConceptsForEntity(entityId), and inferRelationshipPaths(from, to) explain why two things are connected.

This fits search and RAG systems where full-text or vector search finds candidate documents, while graph paths explain semantic relationships. It does not mean every search problem should become a graph problem. Full-text search and vector indexes are still the right tools for document retrieval; graph paths help when relationships and explanations matter.
Social network
Section titled “Social network”The social network example keeps the classic graph problems concrete: Person and Company vertices, with KNOWS,
FOLLOWS, and WORKS_AT edges. It demonstrates direct connections, N-degree traversal, friends-of-friends candidates,
colleagues, shortest paths, all paths, and mutual connections. The example looks simple, but production code quickly adds
the unglamorous rules: exclude the seed user, exclude existing connections, cap depth, and decide whether a relationship
is directional.

The important details are direction, depth, and exclusion rules. FOLLOWS is directional, KNOWS may be treated as
bidirectional by service policy, and recommendation queries should exclude the seed user plus existing connections.
Spring Boot and Ktor integration
Section titled “Spring Boot and Ktor integration”Spring Boot 4 users get GraphOperations, GraphSuspendOperations, and GraphVirtualThreadOperations beans from
graph-spring-boot. TinkerGraph is the default when bluetape4k.graph.backend is missing, which makes local tests and
small examples cheap.
bluetape4k: graph: backend: tinkergraphService code then uses normal bean injection.
@Serviceclass AbuseReviewService( private val graph: GraphOperations,) { fun cluster(seedUserId: GraphElementId): AbuseCluster { val service = AbuserDetectionService(graph) return service.findAbuseCluster(seedUserId) }}For Neo4j, keep the service code and change the storage properties.
bluetape4k: graph: backend: neo4j neo4j: uri: bolt://localhost:7687 username: neo4j password: secret register-suspend: true register-virtual-thread: trueKtor 3 users install GraphPlugin in the application module. The plugin stores the resolved state in application
attributes and closes plugin-owned resources on application stop.
fun Application.module() { install(GraphPlugin) { tinkerGraph() }
routing { get("/users/{id}/cluster") { val graph = graphPluginState().graphOperations val service = AbuserDetectionService(graph) val cluster = service.findAbuseCluster(GraphElementId.of(call.parameters["id"]!!)) call.respond(cluster) } }}If DI or bootstrap code already created the operations pair, pass it directly. Keep the default caller-owned lifecycle,
or set closeOnStop = true when the plugin should close the objects. Resource ownership should be explicit; shutdown is
a poor time to discover that nobody knows who owns the driver.
install(GraphPlugin) { operations(syncOps, suspendOps, closeOnStop = true)}Spring Boot integration is property-plus-bean injection. Ktor integration is plugin-plus-application lifecycle. Pick the one that matches the service runtime.
If you are starting from a relational-only service, do not move everything into a graph at once. Find the feature where variable-depth traversal, shortest path, shared identity, or recommendation candidate generation is making relational queries hard to read. Move that feature first, keep the relational database as source of truth when it already owns the business data, start tests with TinkerGraph, and measure the production candidates with your workload.
Sources
Section titled “Sources”- AbuserDetectionService
- RecommendationService
- knowledge graph workshop
- social network workshop
- Spring Boot graph auto-configuration
- Ktor graph plugin
- GraphProperties
- GraphPluginConfig
Series
Section titled “Series”- Part 1: Database Selection Map
- Part 2: Core API and Execution Models
- Part 3: Graph I/O and Benchmarks
- Part 4: Workshop Scenarios and Service Integration
- Part 5: Reading the Virtual Threads Benchmark
Comments
Leave a note or reaction with your GitHub account.