Manual vs Generated: Code Comparisons

This page shows side-by-side comparisons between manually writing domain interfaces and using Kastor Gen’s automatic generation.

Defining a Catalog Interface

Manual Approach

// You write this manually - 30-60 minutes
@Rdf(iri = "http://www.w3.org/ns/dcat#Catalog")
interface Catalog {
    @Rdf(iri = "http://purl.org/dc/terms/title")
    val title: String  // Did you verify minCount/maxCount?
    
    @Rdf(iri = "http://purl.org/dc/terms/description")
    val description: String?  // Is this really optional?
    
    @Rdf(iri = "http://www.w3.org/ns/dcat#dataset")
    val dataset: List<Dataset>
    
    @Rdf(iri = "http://purl.org/dc/terms/publisher")
    val publisher: Agent?
    
    @Rdf(iri = "http://purl.org/dc/terms/issued")
    val issued: String?  // Should this be a Date type?
    
    // ... 20+ more properties
    // Risk: Typos, wrong IRIs, missing properties, wrong types
}

Time: 30-60 minutes | Risk: High | Maintenance: Manual

Kastor Gen approach

// You configure this - 2 minutes
import com.geoknoesis.kastor.gen.annotations.Rdf

@Rdf(
    shacl = "ontologies/dcat.shacl.ttl",
    context = "ontologies/dcat.context.jsonld",
)
class OntologyGenerator

// Kastor Gen generates the interface automatically
// All properties, correct types, proper nullability

Time: 2 minutes | Risk: Zero | Maintenance: Automatic

Improvement: 90-95% time savings, 100% consistency


Handling Ontology Changes

Manual Approach

When SHACL shape changes:

  1. Read updated SHACL (5 min)
  2. Identify what changed (5 min)
  3. Manually update interface (15-30 min)
  4. Update wrapper code if needed (10-20 min)
  5. Test changes (10 min)
  6. Fix any inconsistencies (10-20 min)
Total: 55-90 minutes Risk: Easy to miss changes

Kastor Gen Approach

When SHACL shape changes:

  1. Update SHACL file (5 min)
  2. Run ./gradlew build (2 min)
  3. Test changes (10 min)
Total: 17 minutes Risk: Zero - all changes automatically applied

Improvement: 70-80% time savings, zero risk of missing changes


Type Safety Examples

Manual (Runtime Errors)

// Manual interface - no compile-time validation
@Rdf(iri = "http://www.w3.org/ns/dcat#Catalog")
interface Catalog {
    @Rdf(iri = "http://purl.org/dc/terms/title")
    val title: String?  // ❌ Wrong! SHACL says minCount 1 (required)
    
    @Rdf(iri = "http://purl.org/dc/terms/description")
    val description: List<String>  // ❌ Wrong! SHACL says maxCount 1
}

// Runtime error when data doesn't match
val catalog: Catalog = catalogRef.asType()
println(catalog.title)  // ❌ NullPointerException if title missing

Kastor Gen (Compile-Time Safety)

// Generated interface - types match SHACL exactly
interface Catalog {
    val title: String  // ✅ Not nullable (minCount 1 from SHACL)
    val description: String?  // ✅ Single value (maxCount 1 from SHACL)
}

// Compile-time safety
val catalog: Catalog = catalogRef.asType()
println(catalog.title)  // ✅ Always has value (type system guarantees it)

Improvement: 100% type safety, zero runtime type errors


Complex Relationships

Manual (Error-Prone)

// Manual interface - easy to get relationships wrong
@Rdf(iri = "http://www.w3.org/ns/dcat#Dataset")
interface Dataset {
    @Rdf(iri = "http://www.w3.org/ns/dcat#distribution")
    val distribution: List<Distribution>  // Is this correct?
    
    @Rdf(iri = "http://purl.org/dc/terms/theme")
    val theme: List<Concept>  // Or is it List<String>?
    
    // ... many more properties
    // Risk: Wrong types, missing relationships
}

Kastor Gen (Automatic)

// Generated interface - relationships from SHACL
interface Dataset {
    val distribution: List<Distribution>  // ✅ Type from SHACL sh:class
    val theme: List<Concept>  // ✅ Correct type from ontology
    // All relationships automatically inferred
}

Improvement: Zero relationship errors, automatic type inference


Maintaining Consistency

Manual (Drift Over Time)

// Month 1: Interface matches ontology
interface Catalog {
    val title: String
    val description: String?
}

// Month 3: Ontology updated, but interface not updated
// SHACL now requires description (minCount 1)
// Interface still has description: String?  // ❌ Inconsistent!

// Month 6: More drift, properties added to ontology
// Interface missing new properties  // ❌ Incomplete!

Problem: Code drifts from ontology over time

Kastor Gen (Always Consistent)

// Month 1: Generated from ontology
interface Catalog {
    val title: String
    val description: String?
}

// Month 3: Regenerate after ontology update
interface Catalog {
    val title: String
    val description: String  // ✅ Automatically updated (now required)
}

// Month 6: Regenerate again
interface Catalog {
    val title: String
    val description: String
    val language: List<String>  // ✅ New property automatically added
}

Improvement: 100% consistency, zero manual sync needed


Working with Multiple Ontologies

Manual (Exponential Complexity)

// You must manually write interfaces for each ontology
@Rdf(iri = "http://www.w3.org/ns/dcat#Catalog")
interface Catalog { /* ... */ }

@Rdf(iri = "http://schema.org/DataCatalog")
interface DataCatalog { /* ... */ }

@Rdf(iri = "http://purl.org/dc/terms/Agent")
interface Agent { /* ... */ }

// ... 50+ more interfaces
// Time: Days or weeks
// Risk: High - many opportunities for errors

Kastor Gen (Linear Complexity)

// Configure once for each ontology
import com.geoknoesis.kastor.gen.annotations.Rdf

@Rdf(
    shacl = "ontologies/dcat.shacl.ttl",
    context = "ontologies/dcat.context.jsonld",
)
class DCATGenerator

@Rdf(
    shacl = "ontologies/schema.shacl.ttl",
    context = "ontologies/schema.context.jsonld",
)
class SchemaGenerator

// All interfaces generated automatically
// Time: Minutes
// Risk: Zero

Improvement: Scales linearly, not exponentially


Summary

Aspect Manual Kastor Gen Improvement
Initial setup 30-60 min/class 2 min/ontology 90-95%
Type safety Runtime Compile-time 100%
Consistency 70-80% 100% 20-30%
Update time 40-65 min 17 min 60-75%
Error rate Baseline -90% Fewer bugs
Maintenance Manual sync Automatic Zero effort

Next Steps: