Skip to content

Latest commit

 

History

History
305 lines (220 loc) · 13.7 KB

wire-annotations.adoc

File metadata and controls

305 lines (220 loc) · 13.7 KB

Chronicle Wire Annotations: A Comprehensive Guide

Purpose: To provide a comprehensive user guide and reference for all standard annotations available in Chronicle Wire, detailing their purpose, usage, and impact on serialisation.

1. Introduction

Chronicle Wire leverages Java annotations to provide developers with fine-grained control over the serialisation and deserialisation process. Annotations offer a declarative way to customise data representation, optimise for performance or size, add metadata, and aid in schema evolution, often without needing to write complex custom marshalling logic.

This document serves as a comprehensive guide to the standard annotations provided by Chronicle Wire. For each annotation, it details:

  • Its primary purpose and intended use case.

  • Where it can be applied (e.g., fields, types).

  • Any attributes or parameters it accepts.

  • Its specific behaviour and impact on different wire formats (text vs. binary).

  • Practical examples to illustrate its usage.

Understanding these annotations is key to effectively tailoring Chronicle Wire to your specific application needs, whether for human readability, extreme performance, or robust data versioning. This guide aims to fulfil the requirements for such documentation by being that guide.

2. Core Annotation Categories

Chronicle Wire annotations can be broadly categorised based on their primary function:

  • Data Conversion & Formatting: These annotations alter how a Java data type is represented on the wire, often to optimise for space, performance, or human readability in text formats.

  • Schema Definition & Metadata: These annotations provide metadata about fields or types, which can influence how data is structured, aid in schema evolution, or add descriptive information.

  • Marshalling Control (Less Common for Direct Wire Annotations): While direct field inclusion/exclusion is often handled by Java’s transient keyword or the inherent behaviour of Marshallable, some annotations might subtly influence what or how data is marshalled.

This guide will explore annotations primarily from the first two categories.

3. Data Conversion & Formatting Annotations

These annotations are crucial for controlling the on-wire representation of your data.

3.1. @LongConversion and Derivative Converters

Purpose

The @LongConversion annotation is a powerful meta-annotation used to specify that a long field in Java should be converted to and from a different representation (typically a String) when serialised to text-based wire formats, while remaining a long in binary formats. This is extremely useful for compactly storing short textual identifiers or codes within an 8-byte long.

Target

Field (of type long)

Attributes
value (Class<? extends LongConverter>)

Specifies the LongConverter implementation class to use for the conversion.

Behaviour
  • Text Formats (YAML, JSON): The LongConverter’s `asString(long) method is called to produce a String representation for the output. During input, parse(CharSequence) is called to convert the String back to a long.

  • Binary Formats: The field is serialised directly as a standard long (typically 8 bytes). The conversion logic is not applied.

Use Case

Storing short alphanumeric IDs, symbols, or codes (e.g., currency pairs, stock tickers, status codes) that can be mapped to a long for efficient storage and processing, but need to be human-readable in configurations or logs.

Example

While @LongConversion is the base, Chronicle Wire provides several convenient pre-defined annotations that use specific converters:

3.1.1. @BaseN Converters (@Base32, @Base64, @Base85)

Annotations

@Base32, @Base64, @Base85 (these are meta-annotated with @LongConversion pointing to respective BaseNLongConverter implementations).

Purpose

To store short strings (composed of characters from the respective BaseN alphabet) within a int, up to 5 characters, or long upt o 10 characters.

  • @Base32: Uses a 32-character alphabet. Can store up to 12 characters in a long.

  • @Base64: Uses a 64-character alphabet (A-Z, a-z, 0-9, +, /). Can store up to 10 characters in a long.

  • @Base85: Uses an 85-character alphabet. Can store up to 10 (sometimes 11 depending on exact character values) characters more efficiently than Base64 for certain character sets.

Example (@Base64)
import net.openhft.chronicle.wire.SelfDescribingMarshallable;
import net.openhft.chronicle.wire.converter.Base64;

public class StockInfo extends SelfDescribingMarshallable {
    @Base64
    private long stockSymbol; // e.g., "GOOGL"
    private double price;

    public StockInfo(String symbolStr, double price) {
        this.stockSymbol = Base64.INSTANCE.parse(symbolStr); // Use the converter instance
        this.price = price;
    }

    public String getStockSymbol() {
        return Base64.INSTANCE.asString(this.stockSymbol);
    }
    // ... price getter
}
# In YAML output:
!StockInfo {
  symbol: GOOGL, // ShortText encoded as Base 85
  price: 123.45  // 64-bit floating point
}

3.1.2. @ShortText

Status

Stable

Purpose

Provides a concise way to convert short strings (up to ten characters) to and from a int up to 5 characters, or long up to 10 characters. using the ShortTextLongConverter. It is a specialised form of @LongConversion and internally relies on the same Base85-based encoding as @Base85.

The difference between @ShortText and @Base85 is that @ShortText truncates leading spaces and @Base85 truncates leading zeros. e.g. @ShortText will convert " 123" to "123" and @Base85 will convert "000123" to "123".

Target

Field (of type long)

Behaviour
  • Text Formats: Values appear as Base 85 strings.

  • Binary Formats: Values are stored as eight-byte longs.

Use Case

Compact storage of short identifiers or codes while keeping them readable in YAML/JSON. It acts as a shorthand for @LongConversion(ShortTextLongConverter.class).

Example (@ShortText)
import net.openhft.chronicle.wire.converter.ShortText;

public class Order extends SelfDescribingMarshallable {
    @ShortText
    private long market;
}

3.2. Timestamp Converters (@NanoTime)

Annotations

@NanoTime

Purpose

To format long fields representing timestamps (nanoseconds or milliseconds since epoch, respectively) as human-readable ISO 8601 date-time strings in text formats, while storing them as long values in binary formats and in Java.

Target

Field (of type long)

Behaviour
  • Text Formats (YAML, JSON): Converts the long timestamp to an ISO 8601 string (e.g., 2023-10-27T10:15:30.123456789Z).

  • Binary Formats: Stores the long value directly.

Use Case

Storing precise timestamps efficiently while allowing easy human inspection in logs or configuration files.

Example (@NanoTime)
import net.openhft.chronicle.wire.SelfDescribingMarshallable;
import net.openhft.chronicle.wire.converter.NanoTime; // The annotation

public class LogEntry extends SelfDescribingMarshallable {
    @NanoTime
    private long eventTimestamp;
    private String message;

    public LogEntry(long eventTimestamp, String message) {
        this.eventTimestamp = eventTimestamp;
        this.message = message;
    }
    // ... getters
}
!LogEntry {
  eventTimestamp: 2025-05-30T07:30:05.123456789Z, // ISO 8601 format
  message: System initialised
}

4. Schema Definition & Metadata Annotations

These annotations provide additional information about fields or types, primarily aiding in schema evolution, binary format stability, or human-readable documentation.

4.1. @FieldNumber

Status

Road Map

Purpose

To assign a persistent, explicit numeric identifier to a field. This is primarily used in some binary wire formats (BINARY, BINARY_LIGHT) where fields might be identified by number instead of, or in addition to, their names.

Target

Field

Attributes
value (int)

The unique numeric identifier for the field.

Behaviour
  • Binary Formats: The specified field number is written to the wire instead of/alongside the field name. This allows the Java field name to be refactored (renamed) without breaking binary compatibility, as long as the @FieldNumber remains the same.

  • Text Formats: Generally has no direct effect on the output (field names are used).

Use Case

Ensuring long-term binary compatibility and allowing safe refactoring of field names in Marshallable classes when using number-based binary field identification.

Example
import net.openhft.chronicle.wire.FieldNumber;
import net.openhft.chronicle.wire.SelfDescribingMarshallable;

public class FinancialInstrument extends SelfDescribingMarshallable {
    @FieldNumber(1)
    private String instrumentId; // Was perhaps "ticker" before, renamed to "instrumentId"

    @FieldNumber(2)
    private double lastPrice;

    // ...
}
Important
Ensure field numbers are unique within a class hierarchy if polymorphism and shared binary formats are involved.

4.2. @FieldOrder

Purpose

To give a Marshallable type an explicit, repeatable field serialisation order instead of relying on reflection order.

Target

Type

Attributes
value (String[])

The names of all non-static, non-transient marshallable fields declared directly on the annotated class, in the order they should be written. Names are trimmed before validation and lookup.

Behaviour
  • Text Formats: Fields are emitted in the declared order, which keeps string and YAML assertions stable across JVMs.

  • Binary Formats: Named-field binary formats use the same marshaller field order when writing fields. Formats that omit field names remain sensitive to sender and receiver schema agreement.

  • Inheritance: The annotation applies only to fields declared directly on the annotated class. Inherited fields keep the order declared by their own class.

  • Validation: Chronicle Wire rejects incomplete lists, duplicate names, blank names, and names that do not resolve to marshallable fields.

Use Case

Stabilising serialised output for classes whose field order is part of a test fixture, text dump, or fieldless/named binary compatibility contract.

Example
import net.openhft.chronicle.wire.FieldOrder;
import net.openhft.chronicle.wire.SelfDescribingMarshallable;

@FieldOrder({"baseId", "createdTime"})
class Base extends SelfDescribingMarshallable {
    long createdTime;
    long baseId;
}

@FieldOrder({"symbol", "price", "quantity"})
class Trade extends Base {
    double price;
    String symbol;
    long quantity;
}

The resulting order is baseId, createdTime, then symbol, price, quantity.

4.3. @Comment

Purpose

To add a descriptive comment to a field, which will be included in some text-based wire formats, typically YAML.

Target

Field

Attributes
value (String)

The comment text.

Behaviour
  • YAML Format: The comment is usually rendered preceding the field.

  • JSON/Binary Formats: This annotation is generally ignored.

Use Case

Improving the readability and self-documentation of configuration files or data dumps generated in YAML format.

Example
import net.openhft.chronicle.wire.Comment;
import net.openhft.chronicle.wire.SelfDescribingMarshallable;

public class AppConfig extends SelfDescribingMarshallable {
    @Comment("The network port for the primary service")
    private int servicePort;

    @Comment("Connection timeout in milliseconds")
    private int connectionTimeoutMs;
    // ...
}
AppConfig {
  # The network port for the primary service
  servicePort: 8080,
  # Connection timeout in milliseconds
  connectionTimeoutMs: 5000
}

5. Best Practices for Using Annotations

  • Be Purposeful: Use annotations where they add clear value, such as improving readability in text formats (@Comment, @NanoTime), optimising binary size/performance (@LongConversion, @FieldNumber), or ensuring schema stability (@FieldNumber, @FieldOrder).

  • Understand Format Impact: Be aware that some annotations only affect text formats (e.g., @Comment), while others primarily impact binary formats (e.g., `@FieldNumber’s core use).

  • Consistency: Apply annotations consistently across related Marshallable classes for predictable behaviour.

  • Test Thoroughly: When using annotations that affect data representation or schema (like @LongConversion, @FieldNumber, or @FieldOrder), thoroughly test both serialisation and deserialisation, especially across different versions of your classes if schema evolution is a concern.

  • Consult Javadoc: The Javadoc for each annotation is the ultimate source of truth for its specific attributes and behaviour in the version of Chronicle Wire you are using.

  • Keep it Simple: While powerful, avoid over-using annotations if simpler solutions suffice. The default behaviour of SelfDescribingMarshallable is often sufficient for many use cases.

This guide provides a solid foundation for understanding and utilising the annotations within Chronicle Wire to build efficient, flexible, and maintainable Java applications.