Schema ​
The Schema is defined in TypeScript using the decorators. The decorator will attach a static meta-property to the class using reflections.
Internal Schema Architecture ​
Internally the schema is represented by RawSchema
, Schema
, AbstractSchema
, and their schema-specific implementations.
📦 @ts-messaging/common
​
RawSchema
​
The RawSchema is the most basic representation of a schema. It is a simple object with a __type
and schema
property and is used internally by the framework for data exchange between packages.
export interface RawSchema{
__type: string; // "AVRO"
schema: any; // {type: "record", name: "User", fields: [...]}
}
SchemaObject
​
The SchemaObject is a TypeScript interface that describes the decoded message type.
export interface SchemaObject {
[key: string]: any;
}
Schema
​
The schema is the common interface between all packages and the minimum type available to the developer.
Each Schema has a unique ID the __id
, a type __type
and the raw schema rawSchema
. The Schema is used to encode
and decode
messages and validate
the message data.
export interface Schema<T extends SchemaObject = SchemaObject> {
readonly __type: string;
readonly __id: string;
readonly rawSchema: RawSchema;
decode(buffer: Buffer): T | null;
encode(data: T): Buffer;
validate(data: T): { success: boolean };
}
SchemaFactory
​
The SchemaFactory
is used by the Registry
to create a Schema
from a RawSchema
. The SchemaFactory
is schema-specific and is implemented by each schema package.
export interface SchemaFactory<SchemaType extends string = string> {
readonly __type: SchemaType;
produce<T extends SchemaObject = SchemaObject>(
config: {
__id: number;
rawSchema: RawSchema<SchemaType>;
}
): Schema<T>;
}
SchemaEntrypoint
​
The SchemaEntrypoint
provides the a Constructor<SchemaFactory>
to the Registry
this constructor is invokes by the Registry
to create a reusable SchemaFactory
for the schema type.
export interface SchemaEntrypoint extends Entrypoint {
readonly schemaFactory: SchemaFactory;
}
📦 @ts-messaging/schema
​
AbstractSchema
​
The AbstractSchema is the base class for all schema implementations. It implements the Schema
interface and provides a default implementation for the findVersion
method using a Registry
.
import { Schema, SchemaObject, Registry } from "@ts-messaging/common";
export abstract class AbstractSchema<T extends SchemaObject = SchemaObject> implements Schema<T> {
abstract readonly __id: number;
abstract readonly __type: string;
abstract readonly rawSchema: RawSchema;
abstract decode(buffer: Buffer): T;
abstract encode(data: T): Buffer;
abstract validate(data: T): { success: boolean };
}
📦 Example: @ts-messaging/schema-avro
​
The @ts-messaging/schema-avro package provides the AvroSchema
implementation of the Schema
interface.
The decoration of a AvroObject
is left to the developer, since packages outside the scope of this package are only looking for the __schema
meta-property, which holds a AvroRawSchema
.
However, a good practice is to define a class decorator like @Avro.Record()
to attach the __schema
meta-property to the class using reflections. The fields for this record can best be specified using property decorators like @Avro.String()
, @Avro.Int()
, ...
.
Example: @Avro.Record()
export function RecordDecoratorFactory(config: {
name?: Name;
namespace?: NameSpace;
doc?: string;
aliases?: string[];
fields: FieldConfig[];
}){
return function RecordDecorator<T extends AvroObject>(target: Constructor<T>) {
// ... implementation
Reflect.defineMetadata("__schema", rawSchema, target);
}
}
Example: @Avro.String()
export function StringDecoratorFactory(config: {
doc?: string;
order?: 'ascending' | 'descending' | 'ignore';
default?: string;
}) {
return function StringDecorator<Target extends object>(
target: Target,
key: TargetMatchPropertyType<Target, string>
) {
// ... implementation
}
}
//The type can be used to match the type of the property
type TargetMatchPropertyType<T, V> = {
[K in keyof T]-?: V extends T[K] ? K : never;
}[keyof T];
AvroSchema
, AvroRawSchema
, and AvroObject
​
import { Schema, RawSchema, Registry, SchemaObject } from '@ts-messaging/common';
import { AbstractSchema } from '@ts-messaging/schema';
export type AvroRawSchema = RawSchema<"AVRO", RecordConfig>;
export interface AvroObject extends SchemaObject {}
export class AvroSchema<T extends AvroObject = AvroObject> extends AbstractSchema<T> {
// ... interface Schema
readonly __type = "AVRO";
readonly rawSchema: AvroRawSchema;
constructor(registry: Registry, rawSchema: AvroRawSchema) {
super(registry);
this.rawSchema = rawSchema;
// ... implementation
}
decode(buffer: Buffer): T | null {
// ... implementation
}
encode(data: T): Buffer {
// ... implementation
}
validate(data: T): { success: boolean } {
// ... implementation
}
}