refcodes-schema: Canonical runtime diagnostics and visitor-based reports

README

The REFCODES.ORG codes represent a group of artifacts consolidating parts of my work in the past years. Several topics are covered which I consider useful for you, programmers, developers and software engineers.

What is this repository for?

This artifact defines a lightweight canonical schema for representing a system’s runtime state and offers visitor interfaces to export that state in multiple notations.

It helps you understand and diagnose complex object graphs without invasive debugging or excessive logging. You model your data as nested Schema instances, optionally expose a simple Schemable façade on your types and let visitors render the result.

How do I get set up?

To get up and running, include the following dependency (without the three dots “…”) in your pom.xml:

1
2
3
4
5
6
7
8
9
<dependencies>
	...
	<dependency>
		<artifactId>refcodes-schema</artifactId>
		<groupId>org.refcodes</groupId>
		<version>4.1.1</version>
	</dependency>
	...
</dependencies>

The artifact is hosted directly at Maven Central. Jump straight to the source codes at Bitbucket. Read the artifact’s javadoc at javadoc.io.

1
2
3
4
5
6
7
8
9
<dependencies>
	...
	<dependency>
		<artifactId>refcodes-schema-alt-introspection</artifactId>
		<groupId>org.refcodes</groupId>
		<version>4.1.1</version>
	</dependency>
	...
</dependencies>

The refcodes-schema-alt-introspection artifact enables schema creation of arbitrary data structures (POJO) without implementing the Schemable interface by extensively using reflection.

The artifact is hosted directly at Maven Central. Jump straight to the source codes at Bitbucket. Read the artifact’s javadoc at javadoc.io.

How do I get started?

The core pieces are:

  • Schema A canonical container for diagnostic data. Holds key/value properties, an optional instance reference and child Schema nodes to form a hierarchy.

  • Schemable An interface for types that can expose their diagnostic view via toSchema().

  • SchemaVisitor<R> A visitor that traverses a Schema tree and produces an output of type R. Implementations include JSON, XML and PlantUML exporters.

Canonical model

You capture runtime state as a nested tree of Schema nodes. Each node can store arbitrary properties (key/value) and may have child nodes for contained or related data. This yields a consistent, tool-friendly view across subsystems.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public final class SensorReading implements Schemable<Properties> {

  private final String name;
  private final int value;

  public SensorReading(String name, int value) {
	this.name = name;
	this.value = value;
  }

  @Override
  public Schema toSchema() {
	return Schema.builder()
		.withAlias("sensorReading")
		.withInstance(this)
		.withDescription("A single sensor reading")
		.withProperty("name", name)
		.withProperty("value", value)
		.build();
  }
}

Composites simply aggregate their children’s Schema:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public final class SensorPanel implements Schemable<Properties> {

  private final List<SensorReading> sensors;

  public SensorPanel(List<SensorReading> sensors) {
	this.sensors = sensors;
  }

  @Override
  public Schema toSchema() {
	var childSchemas = sensors.stream()
		.map(Schemable::toSchema)
		.toArray(Schema[]::new);

	return Schema.builder()
		.withAlias("sensorPanel")
		.withInstance(this)
		.withDescription("Panel with multiple sensors")
		.withChildren(childSchemas)
		.withProperty("count", sensors.size())
		.build();
  }
}

Export with visitors

Once you have a Schema, you can render it in different formats by supplying the corresponding visitor:

1
2
3
4
5
Schema schema = panel.toSchema();

String json = schema.visit(new JsonVisitor());
String xml  = schema.visit(new XmlVisitor());
String puml = schema.visit(new PlantUmlVisitor());
  • JSON/XML give you machine-readable diagnostics for pipelines, dashboards and reports
  • PlantUML gives you diagrams that visualize object graphs for faster human understanding

Serving diagnostics via an interface

You can expose diagnostics safely without enabling full debug mode. For example, via Spring Boot Actuator:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Component
@WebEndpoint(id = "diagnostics")
public class DiagnosticsEndpoint {

  private final Schemable root; // Provide your root of interest

  public DiagnosticsEndpoint(Schemable root) { this.root = root; }

  @ReadOperation
  public WebEndpointResponse<String> plantUml() {
	Schema schema = root.toSchema();
	String puml = schema.visit(new PlantUmlVisitor());
	return new WebEndpointResponse<>(puml, 200, "text/x-plantuml");
  }
}

Alternatively, expose the same data via an MBean and access it with JMX tools such as JConsole.

Snippets of interest

Add ad-hoc properties

1
2
3
4
5
6
7
Schema schema = Schema.builder()
	.withAlias("job")
	.withDescription("Batch job diagnostics")
	.withProperty("id", jobId)
	.withProperty("status", status)
	.withProperty("durationMs", duration)
	.build();

Compose hierarchies

1
2
3
4
Schema root = Schema.builder()
	.withAlias("orderProcessing")
	.withChildren(order.toSchema(), inventory.toSchema(), payment.toSchema())
	.build();

Render and persist

1
2
3
4
5
6
String xml  = root.visit(new XmlVisitor());
String json = root.visit(new JsonVisitor());

// Write to disk or attach to tickets
Files.writeString(Path.of("diag.xml"), xml);
Files.writeString(Path.of("diag.json"), json);

Visualize with PlantUML

1
2
String puml = root.visit(new PlantUmlVisitor());
// Feed into a PlantUML renderer to produce a diagram for your runbook

Design considerations

  • Separation of concerns Diagnostics are modeled once as a canonical schema. Export formats are independent visitors.

  • Low overhead Simple POJOs implement Schemable. No heavy dependencies or annotations required.

  • Extensibility Add your own SchemaVisitor for custom formats or targets.

  • Operational safety Expose read-only snapshots via endpoints or JMX without attaching debuggers or flooding logs.

Further reading

In the above examples, the Schemable interface was implemented with a generic type parameter such as Properties. This type parameter represents a container for custom, potentially implementation-specific configuration values used while generating a Schema.

The Schemable interface provides a default toSchema(OPTS options) method which accepts such an options object. This allows schema generation to be tailored at runtime using additional parameters that are not known at design time.

The refcodes-runtime artifact builds upon this concept by introducing the Diagnosable interface. The Diagnosable interface extends Schemable by concretizing the generic type parameter OPTS with the Options class.

Options provides a flexible mechanism for supplying configuration values: options can be set programmatically, but may also be resolved transparently from JVM system properties or environment variables. This makes it particularly well suited for runtime diagnostics and tooling scenarios.

The blog post Output runtime diagnostics data as JSON, XML or PlantUML diagrams describes the runtime diagnostics usage and on how to take advantage of this approach (see Fully provisioned runtime diagnostics).

Examples

For examples and usage, please take a look at the according Unit-Tests here. For examples and usage on the introspection alternative, please take a look at the according Unit-Tests here.

See also

Contribution guidelines

  • Report issues
  • Finding bugs
  • Helping fixing bugs
  • Making code and documentation better
  • Enhance the code

Who do I talk to?

Licensing Philosophy

This project follows a dual-licensing model designed to balance openness, pragmatism and fair attribution.

You may choose between the LGPL v3.0 or later and the Apache License v2.0 when using this software.

The intention behind this model is simple:

  • Enable use in both open-source and proprietary projects
  • Keep the codebase approachable and reusable
  • Ensure that improvements to the library itself remain available to the community
  • Preserve clear attribution to the original author and the ecosystem

Under the LGPL v3.0+, you are free to use this library in any application. If you modify the library itself, those modifications must be made available under the same license and must retain proper attribution.

Alternatively, the Apache License v2.0 allows broad use, modification and distribution, including commercial usage, provided that copyright notices and the accompanying NOTICE file are preserved.

This dual-licensing approach intentionally avoids artificial barriers while discouraging closed, uncredited forks of the core library. Contributions, improvements and refinements are encouraged to flow back into the project, benefiting both the community and downstream users.

For licensing questions, alternative licensing arrangements or commercial inquiries, please contact the copyright holder.