Nils Helmig / BareNET · GitLab
source link: https://gitlab.com/nilshelmig/barenet
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
BareNET | BareFs | CLI
.NET-Implementation of Binary Application Record Encoding (BARE) - https://baremessages.org/
Implements the specification from the draft version 1.
In the words of the BARE creator:
In general, JSON messages are pretty bulky. They represent everything as text, which can be 2x as inefficient for certain kinds of data right off the bat. They’re also self-describing: the schema of the message is encoded into the message itself; that is, the names of fields, hierarchy of objects, and data types.
There are many alternatives that attempt to address this problem, and I considered many of them. Here were a selected few of my conclusions:
- protobuf: too complicated and too fragile, and I’ve never been fond of the generated code for protobufs in any language. Writing a third-party protobuf implementation would be a gargantuan task, and there’s no standard. RPC support is also undesirable for this use-case.
- Cap’n Proto: fixed width, alignment, and so on — good for performance, bad for message size. Too complex. RPC support is also undesirable for this use-case. I also passionately hate C++ and I cannot in good faith consider something which makes it their primary target.
- BSON: MonogoDB implementation details have leaked into the specification, and it’s extensible in the worst way. I appreciate that JSON is a closed spec and no one is making vendor extensions for it — and, similarly, a diverse extension ecosystem is not something I want to see for this technology. Additionally, encoding schema into the message is wasting space.
- MessagePack: ruled out for similar reasons: too much extensibility, and the schema is encoded into the message, wasting space.
- CBOR: ruled out for similar reasons: too much extensibility, and the schema is encoded into the message. Has the advantage of a specification, but the disadvantage of that spec being 54 pages long.
For a good introduction read the full blogpost.
Features
- Optimized for small messages: messages are binary, not self-describing, and have no alignment or padding.
- Standardized & simple: the specification is just over 1,000 words.
- Universal: there is room for user extensibility, but it’s done in a manner which does not require expanding the implementation nor making messages which are incompatible with other implementations.
- Zero dependencies.
- Parsing of BARE-schemas.
- Code generation of types and encoding/decoding methods out of BARE-schemas.
- Code generator supports deeply nested anonymous structs and unions.
- Code generator supports usage of types outside of code generation. More infos here[C#] or here[F#]
Usage
Disclaimer
This document will not cover BARE basics. Please refer to the official documentation.
Use in F# or Fable projects
For detailed information see the BareFs Documentation.
Install package
With dotnet-CLI
dotnet add package BareFs
With paket
paket add BareFs
Install code generator
The code generator is available as dotnet tool and requires .NET 5!
dotnet tool install bare-cli
Generate code
Generate Code and save it to Messages.fs
.
echo "type Person { Name: string Age: i32 }" | dotnet bare --stdin --lang fs > Messages.fs
The content of Message.fs
would be
//////////////////////////////////////////////////
// Generated code by BareNET - 13.03.2021 01:02 //
//////////////////////////////////////////////////
namespace Bare.Msg
type Person =
{
Name: string
Age: int
}
module Encoding =
let ofPerson (value: Person) : BareNET.Encoding_Result =
(BareNET.Bare.success (BareNET.Bare.encode_string)) value.Name
|> BareNET.Bare.andThen (BareNET.Bare.success (BareNET.Bare.encode_i32)) value.Age
let decode_Person : BareNET.Decoder<Person> =
BareNET.Bare.decode_complex (fun (name: string) (age: int) -> { Name = name ; Age = age })
|> BareNET.Bare.apply (BareNET.Bare.decode_string)
|> BareNET.Bare.apply (BareNET.Bare.decode_i32)
let toPerson (data: byte array) : Result<Person, string> =
decode_Person data |> Result.map fst
The common way is to store the schema into a
schema.bare
file and provide the file path to the bare-cli. Seedotnet bare --help
for more information!
Remember to include the generated file in the F# project
Do messaging
open Bare.Msg // Default namespace for generated code
let person : Person = { Name = "Nils Helmig" ; Age = 25 }
match person |> Encoding.ofPerson with // Encoding is the generated module for encoders and decoders
| Ok encoded -> // encoded : byte array
// send bytes over a socket
socket.SendBytes encoded
// or store them in a file
encoded |> File.WriteTo "person.bin"
// or maybe encrypt the data
encryptBytes encoded
| Error error ->
printf "The encoding of Person failed with %s" error
let data : byte array = // bytes received from a socket, read from or file, or another way.
match data |> Encoding.toPerson with
| Ok person ->
printf "Here we have the %i old %s" person.Age person.Name
| Error error ->
printf "The decoding of Person failed with %s" error
What about types like Guid
, DateTime
or decimal
?
bare-cli provides a way to use those types in combination with generated types. See BareFs Documentation
Use in C# projects
For detailed information see the BareNET Documentation.
Install package
With dotnet-CLI
dotnet add package BareNET
With paket
paket add BareNET
Install code generator
The code generator is available as dotnet tool and requires .NET 5!
dotnet tool install bare-cli
Generate code
Generate Code and save it to Messages.cs
.
echo "type Person { Name: string Age: i32 }" | dotnet bare --stdin > Messages.cs
The content of Message.cs
would be
//////////////////////////////////////////////////
// Generated code by BareNET - 13.03.2021 01:01 //
//////////////////////////////////////////////////
using System;
using System.Linq;
using System.Collections.Generic;
namespace Bare.Msg
{
public readonly struct Person
{
public readonly string Name;
public readonly int Age;
public Person(string name, int age)
{
Name = name;
Age = age;
}
public byte[] Encoded()
{
return BareNET.Bare.Encode_string(Name)
.Concat(BareNET.Bare.Encode_i32(Age))
.ToArray();
}
public static Person Decoded(byte[] data) { return Decode(data).Item1; }
public static ValueTuple<Person, byte[]> Decode(byte[] data)
{
var name = BareNET.Bare.Decode_string(data);
var age = BareNET.Bare.Decode_i32(name.Item2);
return new ValueTuple<Person, byte[]>(
new Person(name.Item1, age.Item1),
age.Item2);
}
}
}
The common way is to store the schema into a
schema.bare
file and provide the file path to the bare-cli. Seedotnet bare --help
for more information!
Do messaging
using Bare.Msg; // Default namespace for generated code
var person = new Person(name: "Nils Helmig", age: 25);
try
{
var encoded = person.Encoded(); // Structs have an Encoded Method. Unions and enums encoders are available in the generated static `Encoding` class
// send bytes over a socket
socket.SendBytes(encoded);
// or store them in a file
file.WriteAll(encoded, "person.bin");
// or maybe encrypt the data
encryptBytes(encoded);
}
catch (ArgumentException ex)
{
Console.WriteLine($"The encoding of Person failed with {ex.Message}");
}
byte[] data = // bytes received from a socket, read from or file, or another way.
try
{
var person = Person.Decoded(data);
Console.WriteLine($"Here we have the {person.Age} old {person.Name}");
}
catch (ArgumentException ex)
{
Console.WriteLine($"The decoding of Person failed with {ex.Message}");
}
What about types like Guid
, DateTime
or decimal
?
bare-cli provides a way to use those types in combination with generated types. See BareNET Documentation
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK