-
Notifications
You must be signed in to change notification settings - Fork 523
Golang User Guide
After running the SbeTool a number of Golang source files will be created. These files represent the types and messages declared in the schema. For a quick start to SBE look at this example schema schema and its usage here.
Messages are designed to be read in the sequential order as defined in the schema. This ensures a stream access pattern for performance. If groups, or variable data, are not processed in order then the data may become corrupt. Conceptually a message is encoded as a series of blocks. The blocks are the root fields, followed by each iteration of repeating groups, and finally followed by one or more variable data fields.
Due to the streaming nature of the codec the encoded length of the message cannot be determined until encoding or decoding is complete.
Note: It is important to encode and decode elements in the schema order, otherwise undefined behaviour can occur. This is especially important to repeating groups and variable length data fields as they modify internal state for the position within the message.
It is expected that the messages are communicated inside a framing protocol. The frame defines the size of the buffer containing the message header and message itself.
+------------------------------------------------------------+
| +----------+----------------------------------+ |
|Frame |Msg Header|Message Body | |
| +----------+----------------------------------+ |
+------------------------------------------------------------+
The frame may contain session or transport level fields that belong to different layers of the OSI Model and beyond the scope of the message codec which deals with the layer 6 presentation.
The message header contains the fields that allows the decoder to identify what codec should be used as the template for a message.
- BlockLength: The length of the message root block before repeating groups or variable data commences.
- TemplateId: The identifier for the template type of the message that is to follow.
- SchemaId: The identifier for the schema the message belongs to.
- Version: The version of the schema allowing for extension.
Note: A message header type can be defined with other fields and with different
sizes of integers for the template and version according to needs. A MessageHeader
type will always be generated and can be used but if a standard message header is used an
optimized SbeGoMessageHeader
type is made generally available.
To encode a message it is first necessary to encode the header. Using the Car from the example-schema:
var car Car
var buf = new(bytes.Buffer)
m := NewSbeGoMarshaller()
header := SbeGoMessageHeader{car.SbeBLockLength(), car.SbeTemplateId(), car.SbeSchemaId(), car.SbeSchemaVersion()}
header.Encode(m, buf)
and then for a (populated) car:
var car Car
// populate car
CarInit(car) // populate constant value fields
if err:= car.Encode(m, buf, false); err != nil {
/// handle errors
}
Some notes of interest:
The marshaller m
in the above example can be used for both
encoding and decoding and it contains state. As a result each goroutine
should use it's own marshaller but they can be reused within an existing
goroutine for both encoding and decoding.
Here we use a bytes.Buffer
type to hold our marshalled data but
this can be any io.Writer
such as a socket, pipe etc and can
optionally add a bufio wrapper to coalesce write operations etc. You
can see examples of this
here
The two Encode()
methods here have slightly different signatures
with the car's Encode()
method having an additional boolean
doRangeCheck
argument. Messages have this argument for both
Encode()
and Decode()
methods. Message range checking
guarantees that on encode no bytes are written to the output stream if
range checking fails and that on decode an entire message will have
been read from the input stream.
Types other than messages (e.g., groups, varadata) provide a
RangeCheck()
method used by the message encoders. It is imagined
that range checking would be used during development to ensure
messages are valid within the spec. For usage where performance is not
paramount it may be left enabled in production.
Each generated type Foo
will have a FooInit()
method to
populate constant value fields. It is only necessary to call this for
types that have constant value fields.
The decoder decodes the header and can then lookup which template should be used to decode the message body.
var header SbeGoMessageHeader
m := NewSbeGoMarshaller()
header.Decode(m, reader)
// Lookup template for decoding the message and determine it is a car
var car Car
if err := car.Decode(m, reader, header.SbeSchemaVersion(), header.SbeBlockLength(), true); err != nil {
// error handling
}
The Golang generator does not produce a flyweight implementation as for Java and C++, but instead creates types with methods. This allows the golang implementation to use the standard idiomatic constructs such as channels for io, goroutines for multiplexing, etc.
This section describes the mappings between SBE types and golang.
Single fixed fields are represented as a field in a type and are directly accessed e.g.,
car.SerialNumber = 1234
model := car.ModelYear
Integer types (both signed and unsigned) are explicitly sized (e.g., uint16
, int64
so they are correctly represented on the wire.
Single characters are represented as a byte
.
SBE fixed length arrays are represented as golang fixed length arrays and can be referenced as such e.g.,
for i := 0; i < len(car.SomeNumbers); i++ {
car.SomeNumbers[i] = i;
}
Fixed length string arrays are represented as fixed length byte arrays
in golang. There is no default character encoding but if the character
encoding is specified then validity may be performed by the
RangeCheck()
method. Currently validated character encodings are
UTF-8 and ASCII.
Constants are not encoded and decoded. Their value, as defined in the
schema, is set automatically on decode and can be set using the
TypeInit()
method described above for encoding.
Enumerations in the message schema do not map directly as golang does not support an enumerated type. Instead they are represented as a type and a variable containing the known constant values. From the Car example there is a Model enumeration generated:
type ModelEnum byte
type ModelValues struct {
A ModelEnum
B ModelEnum
C ModelEnum
NullValue ModelEnum
}
var Model = ModelValues{65, 66, 67, 0}
Using this would look like:
car.DiscountedModel = Model.C
Range checking of enumeration values is only performed when the SchemaVersion is equal to or greater than the ActingVersion so that new enumeration values are allowed to propagate to older decoders.
A bitset is multi-value choice that is encoded to the wire as bits in an integer which represent their presence or not (1 vs 0). Golang does not support bitsets and so they are represented in code as an array of boolean values. Again from the Car example the generated code looks like:
type OptionalExtras [8]bool
type OptionalExtrasChoiceValue uint8
type OptionalExtrasChoiceValues struct {
SunRoof OptionalExtrasChoiceValue
SportsPack OptionalExtrasChoiceValue
CruiseControl OptionalExtrasChoiceValue
}
var OptionalExtrasChoice = OptionalExtrasChoiceValues{0, 1, 2}
and the usage is similar to enumerations:
car.Extras[OptionalExtrasChoice.CruiseControl] = true
Composite types provide a means of reuse. They map directly to a type in Golang and are simply referenced e.g.,
car.Engine.Capacity = 17
cylinders = car.Engine.NumCylinders
Repeating groups allow for collections of repeating type which can even be nested. The groups are types represented as slices where the length of the slice determines the number in the group. e.g.,
groupLength := len(car.FuelFigures)
// Grab the second element's Speed
speed := car.FuelFigures[1].Speed
Variable Length data is used in SBE to represent binary data (and sometimes variable length strings). In golang these are also represented as slices and behave as for groups above.
The golang generator provides no special support for semantic types (as for Java and C++).
The Boolean
semantic type is not represented as a golang bool
as the
spec requires that discrepancies between SinceVersion and
ActingVersion can be resolved using the NullValue.
The String
semantic type is simply represented as a byte array as
golang Strings are immutable and are not useful in structures.
The UTCTimestamp
, UTCTimeOnly
, UTCDateOnly
and MktTime
semantic types
are not represented as a golang Time
type as none of these semantic
types contain a location. Mapping to a golang Time
type will be
relatively easy for the application developer who will know the
location and can set it accordingly (null is UTC). They can also
decide what to with golang Time
's nanosecond timer when converting
(probably treating it as zero).