Advanced Usage Patterns
This tutorial covers advanced usage patterns, complex scenarios, and sophisticated techniques for working with Kastor Gen in production applications.
Complex Domain Models
Hierarchical Domain Models
Model complex hierarchical relationships:
@Rdf(iri = "http://example.org/Organization")
interface Organization {
@Rdf(iri = "http://example.org/name")
val name: List<String>
@Rdf(iri = "http://example.org/description")
val description: List<String>
@Rdf(iri = "http://example.org/parentOrganization")
val parentOrganization: List<Organization>
@Rdf(iri = "http://example.org/subOrganization")
val subOrganizations: List<Organization>
@Rdf(iri = "http://example.org/department")
val departments: List<Department>
@Rdf(iri = "http://example.org/employee")
val employees: List<Employee>
}
@Rdf(iri = "http://example.org/Department")
interface Department {
@Rdf(iri = "http://example.org/name")
val name: List<String>
@Rdf(iri = "http://example.org/organization")
val organization: List<Organization>
@Rdf(iri = "http://example.org/manager")
val manager: List<Employee>
@Rdf(iri = "http://example.org/employee")
val employees: List<Employee>
}
@Rdf(iri = "http://example.org/Employee")
interface Employee {
@Rdf(iri = "http://xmlns.com/foaf/0.1/name")
val name: List<String>
@Rdf(iri = "http://example.org/employeeId")
val employeeId: List<String>
@Rdf(iri = "http://example.org/organization")
val organization: List<Organization>
@Rdf(iri = "http://example.org/department")
val department: List<Department>
@Rdf(iri = "http://example.org/manager")
val manager: List<Employee>
@Rdf(iri = "http://example.org/subordinate")
val subordinates: List<Employee>
}
Polymorphic Collections
Handle collections of different types:
@Rdf(iri = "http://example.org/Event")
interface Event {
@Rdf(iri = "http://example.org/name")
val name: List<String>
@Rdf(iri = "http://example.org/participant")
val participants: List<Person> // Can be Employee, Customer, or Person
}
@Rdf(iri = "http://example.org/Project")
interface Project {
@Rdf(iri = "http://example.org/name")
val name: List<String>
@Rdf(iri = "http://example.org/stakeholder")
val stakeholders: List<Person> // Different types of stakeholders
}
// Usage with type checking
fun processEvent(event: Event) {
event.participants.forEach { participant ->
when (participant) {
is Employee -> {
println("Employee: ${participant.employeeId.firstOrNull()}")
println("Department: ${participant.department.firstOrNull()?.name?.firstOrNull()}")
}
is Customer -> {
println("Customer: ${participant.customerId.firstOrNull()}")
println("Loyalty Points: ${participant.loyaltyPoints.firstOrNull()}")
}
else -> {
println("Person: ${participant.name.firstOrNull()}")
}
}
}
}
Temporal Data Modeling
Model temporal aspects of data:
@Rdf(iri = "http://example.org/Product")
interface Product {
@Rdf(iri = "http://example.org/name")
val name: List<String>
@Rdf(iri = "http://example.org/price")
val price: List<Double>
@Rdf(iri = "http://example.org/version")
val version: List<String>
@Rdf(iri = "http://example.org/effectiveDate")
val effectiveDate: List<String>
@Rdf(iri = "http://example.org/expirationDate")
val expirationDate: List<String>
@Rdf(iri = "http://example.org/previousVersion")
val previousVersion: List<Product>
@Rdf(iri = "http://example.org/nextVersion")
val nextVersion: List<Product>
}
// Helper functions for temporal operations
fun Product.isCurrentlyValid(): Boolean {
val now = LocalDate.now()
val effective = effectiveDate.firstOrNull()?.let { LocalDate.parse(it) }
val expiration = expirationDate.firstOrNull()?.let { LocalDate.parse(it) }
return when {
effective != null && now.isBefore(effective) -> false
expiration != null && now.isAfter(expiration) -> false
else -> true
}
}
fun Product.getCurrentPrice(): Double? {
return if (isCurrentlyValid()) {
price.firstOrNull()
} else {
null
}
}
Advanced Materialization Patterns
Custom Materialization Logic
Implement custom materialization for complex scenarios:
class CustomMaterializationService {
fun materializeWithContext<T : Any>(
ref: RdfRef,
type: Class<T>,
context: MaterializationContext
): T {
// Create custom handle with context
val handle = ContextualRdfHandle(
node = ref.node,
graph = ref.graph,
context = context
)
// The factory registry is private: materialize through OntoMapper and
// use the handle for context-specific checks around it.
return OntoMapper.materialize(ref, type)
}
}
data class MaterializationContext(
val user: String? = null,
val permissions: Set<String> = emptySet(),
val filters: Map<String, Any> = emptyMap(),
val options: MaterializationOptions = MaterializationOptions()
)
data class MaterializationOptions(
val includeInactive: Boolean = false,
val maxDepth: Int = 10,
val validate: Boolean = true
)
class ContextualRdfHandle(
override val node: RdfTerm,
override val graph: RdfGraph,
private val context: MaterializationContext
) : RdfHandle {
override val extras: PropertyBag by lazy {
ContextualPropertyBag(graph, node, context)
}
override fun validate(): ValidationResult {
if (!context.options.validate) return ValidationResult.Ok
return validation.validate(graph, node)
}
}
Lazy Loading with Pagination
Implement lazy loading for large datasets:
class PaginatedMaterializationService {
fun materializePage<T : Any>(
graph: RdfGraph,
type: Class<T>,
page: Int,
pageSize: Int
): Page<T> {
val triples = graph.getTriples()
val typeTriples = triples.filter {
it.predicate == RDF.type && it.obj == getTypeIri(type)
}
val startIndex = page * pageSize
val endIndex = minOf(startIndex + pageSize, typeTriples.size)
val pageTriples = typeTriples.subList(startIndex, endIndex)
val items = pageTriples.mapNotNull { triple ->
try {
val ref = RdfRef(triple.subject, graph)
ref.asType<T>()
} catch (e: Exception) {
null
}
}
return Page(
items = items,
page = page,
pageSize = pageSize,
totalItems = typeTriples.size,
totalPages = (typeTriples.size + pageSize - 1) / pageSize
)
}
private fun getTypeIri(type: Class<*>): Iri {
// Extract type IRI from annotation
val annotation = type.getAnnotation(Rdf::class.java)
return iri(annotation.iri)
}
}
data class Page<T>(
val items: List<T>,
val page: Int,
val pageSize: Int,
val totalItems: Int,
val totalPages: Int
)
Advanced RDF Operations
Graph Traversal
Implement graph traversal algorithms:
class GraphTraversalService {
fun findShortestPath(
graph: RdfGraph,
start: RdfTerm,
end: RdfTerm,
predicate: Iri
): List<RdfTerm>? {
val queue = mutableListOf(listOf(start))
val visited = mutableSetOf<RdfTerm>()
while (queue.isNotEmpty()) {
val path = queue.removeAt(0)
val current = path.last()
if (current == end) {
return path
}
if (visited.contains(current)) {
continue
}
visited.add(current)
// Find neighbors
val neighbors = graph.getTriples()
.filter { it.subject == current && it.predicate == predicate }
.map { it.obj as? RdfTerm }
.filterNotNull()
neighbors.forEach { neighbor ->
if (!visited.contains(neighbor)) {
queue.add(path + neighbor)
}
}
}
return null
}
fun findCycles(graph: RdfGraph, predicate: Iri): List<List<RdfTerm>> {
val cycles = mutableListOf<List<RdfTerm>>()
val visited = mutableSetOf<RdfTerm>()
val recursionStack = mutableSetOf<RdfTerm>()
fun dfs(node: RdfTerm, path: List<RdfTerm>) {
visited.add(node)
recursionStack.add(node)
val neighbors = graph.getTriples()
.filter { it.subject == node && it.predicate == predicate }
.map { it.obj as? RdfTerm }
.filterNotNull()
neighbors.forEach { neighbor ->
if (recursionStack.contains(neighbor)) {
// Found a cycle
val cycleStart = path.indexOf(neighbor)
if (cycleStart >= 0) {
cycles.add(path.subList(cycleStart, path.size) + neighbor)
}
} else if (!visited.contains(neighbor)) {
dfs(neighbor, path + neighbor)
}
}
recursionStack.remove(node)
}
val allNodes = graph.getTriples()
.flatMap { listOf(it.subject, it.obj) }
.filterIsInstance<RdfTerm>()
.distinct()
allNodes.forEach { node ->
if (!visited.contains(node)) {
dfs(node, listOf(node))
}
}
return cycles
}
}
RDF Inference
Implement basic RDF inference:
class InferenceService {
fun inferTransitiveProperties(
graph: RdfGraph,
predicate: Iri
): RdfGraph {
val inferredGraph = Rdf.memory()
val triples = graph.getTriples()
// Copy original triples
inferredGraph.add {
triples.forEach { triple ->
triple.subject - triple.predicate - triple.obj
}
}
// Infer transitive properties
val transitiveTriples = mutableSetOf<RdfTriple>()
val processed = mutableSetOf<RdfTerm>()
fun inferTransitive(node: RdfTerm) {
if (processed.contains(node)) return
processed.add(node)
val directTargets = triples
.filter { it.subject == node && it.predicate == predicate }
.map { it.obj as? RdfTerm }
.filterNotNull()
directTargets.forEach { target ->
// Add direct relationship
transitiveTriples.add(RdfTriple(node, predicate, target))
// Recursively infer transitive relationships
inferTransitive(target)
// Add transitive relationships
val transitiveTargets = triples
.filter { it.subject == target && it.predicate == predicate }
.map { it.obj as? RdfTerm }
.filterNotNull()
transitiveTargets.forEach { transitiveTarget ->
transitiveTriples.add(RdfTriple(node, predicate, transitiveTarget))
}
}
}
// Start inference from all nodes
val allNodes = triples
.flatMap { listOf(it.subject, it.obj) }
.filterIsInstance<RdfTerm>()
.distinct()
allNodes.forEach { node ->
inferTransitive(node)
}
// Add inferred triples
inferredGraph.add {
transitiveTriples.forEach { triple ->
triple.subject - triple.predicate - triple.obj
}
}
return inferredGraph.defaultGraph
}
}
Performance Optimization
Caching Strategies
Implement intelligent caching:
class CachingMaterializationService {
private val materializationCache = LruCache<CacheKey, Any>(1000)
private val propertyCache = LruCache<PropertyKey, Any>(10000)
fun materializeWithCache<T : Any>(
ref: RdfRef,
type: Class<T>
): T {
val cacheKey = CacheKey(ref.node, ref.graph, type)
return materializationCache.get(cacheKey) {
OntoMapper.materialize(ref, type)
} as T
}
private inner class CachingRdfHandle(
override val node: RdfTerm,
override val graph: RdfGraph
) : RdfHandle {
override val extras: PropertyBag by lazy {
CachingPropertyBag(graph, node)
}
override fun validate(): ValidationResult {
return validation.validate(graph, node)
}
}
private inner class CachingPropertyBag(
private val graph: RdfGraph,
private val node: RdfTerm
) : PropertyBag {
private val cache = mutableMapOf<Iri, List<RdfTerm>>()
override fun predicates(): Set<Iri> {
return getCachedTriples().keys.toSet()
}
override fun values(pred: Iri): List<RdfTerm> {
return getCachedTriples()[pred] ?: emptyList()
}
override fun literals(pred: Iri): List<Literal> {
return values(pred).filterIsInstance<Literal>()
}
override fun strings(pred: Iri): List<String> {
return literals(pred).map { it.lexical }
}
override fun iris(pred: Iri): List<Iri> {
return values(pred).filterIsInstance<Iri>()
}
override fun <T : Any> objects(pred: Iri, asType: Class<T>): List<T> {
return values(pred).mapNotNull { term ->
when (term) {
is Iri, is BlankNode -> {
try {
OntoMapper.materialize(RdfRef(term, graph), asType)
} catch (e: Exception) {
null
}
}
else -> null
}
}
}
private fun getCachedTriples(): Map<Iri, List<RdfTerm>> {
if (cache.isEmpty()) {
val triples = graph.getTriples()
.filter { it.subject == node }
.groupBy { it.predicate }
.mapValues { (_, triples) -> triples.map { it.obj } }
cache.putAll(triples)
}
return cache
}
}
data class CacheKey(
val node: RdfTerm,
val graph: RdfGraph,
val type: Class<*>
)
data class PropertyKey(
val node: RdfTerm,
val graph: RdfGraph,
val predicate: Iri
)
}
Async Materialization
Implement asynchronous materialization:
class AsyncMaterializationService {
private val executor = Executors.newFixedThreadPool(10)
fun materializeAsync<T : Any>(
ref: RdfRef,
type: Class<T>
): CompletableFuture<T> {
return CompletableFuture.supplyAsync({
ref.asType<T>()
}, executor)
}
fun materializeBatchAsync<T : Any>(
refs: List<RdfRef>,
type: Class<T>
): CompletableFuture<List<T>> {
val futures = refs.map { ref ->
materializeAsync(ref, type)
}
return CompletableFuture.allOf(*futures.toTypedArray())
.thenApply { futures.map { it.get() } }
}
fun shutdown() {
executor.shutdown()
}
}
Integration Patterns
Repository Pattern with Kastor Gen
interface Repository<T, ID> {
fun findById(id: ID): T?
fun findAll(): List<T>
fun save(entity: T): T
fun delete(id: ID): Boolean
}
class OntoMapperRepository<T : Any>(
private val type: Class<T>,
private val repository: RdfRepository
) : Repository<T, Iri> {
override fun findById(id: Iri): T? {
return try {
val ref = RdfRef(id, repository.defaultGraph)
ref.asType<T>()
} catch (e: Exception) {
null
}
}
override fun findAll(): List<T> {
val graph = repository.defaultGraph
val typeIri = getTypeIri(type)
val entities = graph.getTriples()
.filter { it.predicate == RDF.type && it.obj == typeIri }
.map { it.subject as? Iri }
.filterNotNull()
return entities.mapNotNull { iri ->
try {
RdfRef(iri, graph).asType<T>()
} catch (e: Exception) {
null
}
}
}
override fun save(entity: T): T {
val rdfHandle = (entity as RdfBacked).rdf
val graph = rdfHandle.graph
// Add or update triples
graph.add {
val node = rdfHandle.node
// Add type triple
node - RDF.type - getTypeIri(type)
// Add property triples based on entity
addPropertyTriples(node, entity)
}
return entity
}
override fun delete(id: Iri): Boolean {
val graph = repository.defaultGraph
val triplesToRemove = graph.getTriples()
.filter { it.subject == id }
triplesToRemove.forEach { triple ->
graph.remove(triple)
}
return triplesToRemove.isNotEmpty()
}
private fun getTypeIri(type: Class<*>): Iri {
val annotation = type.getAnnotation(Rdf::class.java)
return iri(annotation.iri)
}
private fun addPropertyTriples(node: RdfTerm, entity: T) {
// Implementation depends on entity structure
// This is a simplified version
}
}
Service Layer Pattern
class PersonService(
private val personRepository: OntoMapperRepository<Person, Iri>,
private val validationService: ValidationService
) {
fun createPerson(name: String, age: Int, email: String): Person {
val repo = Rdf.memory()
val personId = iri("http://example.org/person/${UUID.randomUUID()}")
repo.add {
personId - RDF.type - FOAF.Person
personId - FOAF.name - name
personId - FOAF.age - age
personId - FOAF.mbox - email
personId - DCTERMS.created - "2024-01-15"^^XSD.date
}
val person = RdfRef(personId, repo.defaultGraph).asType<Person>()
// Validate
validationService.validatePerson(person)
return person
}
fun updatePerson(person: Person, updates: Map<String, Any>): Person {
val rdfHandle = person.asRdf()
val graph = rdfHandle.graph
graph.add {
val node = rdfHandle.node
updates.forEach { (key, value) ->
when (key) {
"name" -> node - FOAF.name - value.toString()
"age" -> node - FOAF.age - (value as Int)
"email" -> node - FOAF.mbox - value.toString()
}
}
node - DCTERMS.modified - "2024-01-16"^^XSD.date
}
// Validate
validationService.validatePerson(person)
return person
}
fun findPersonByEmail(email: String): Person? {
val graph = personRepository.repository.defaultGraph
val personTriples = graph.getTriples()
.filter { it.predicate == FOAF.mbox && it.obj == Literal(email) }
return personTriples.firstOrNull()?.let { triple ->
try {
RdfRef(triple.subject, graph).asType<Person>()
} catch (e: Exception) {
null
}
}
}
}
Error Handling and Resilience
Circuit Breaker Pattern
class CircuitBreakerMaterializationService {
private val circuitBreaker = CircuitBreaker.ofDefaults("materialization")
fun materializeWithCircuitBreaker<T : Any>(
ref: RdfRef,
type: Class<T>
): T {
return circuitBreaker.executeSupplier {
ref.asType<T>()
}
}
fun materializeWithFallback<T : Any>(
ref: RdfRef,
type: Class<T>,
fallback: () -> T
): T {
return try {
materializeWithCircuitBreaker(ref, type)
} catch (e: Exception) {
fallback()
}
}
}
Retry Pattern
class RetryMaterializationService {
private val retryTemplate = RetryTemplate.builder()
.maxAttempts(3)
.exponentialBackoff(1000, 2, 10000)
.retryOn(Exception::class.java)
.build()
fun materializeWithRetry<T : Any>(
ref: RdfRef,
type: Class<T>
): T {
return retryTemplate.execute { context ->
try {
ref.asType<T>()
} catch (e: Exception) {
if (context.retryCount >= 2) {
throw e
}
throw e
}
}
}
}
Best Practices Summary
✅ Do
- Use appropriate collection types for your use case
- Implement caching for performance-critical operations
- Handle errors gracefully with fallbacks
- Use async operations for I/O-intensive tasks
- Implement proper validation
- Design for extensibility
- Use repository pattern for data access
- Implement circuit breakers for resilience
❌ Don’t
- Ignore performance implications
- Mix RDF operations with domain logic
- Assume operations always succeed
- Create deep object graphs without limits
- Ignore memory usage with large datasets
- Skip validation in production
- Use blocking operations in async contexts
Next Steps
- See Practical Examples - Real-world use cases
- Review API Reference - Complete API documentation
- Learn about Best Practices - Guidelines for effective usage
- Check out FAQ - Common questions and answers