3

Nils Helmig / BareNET · GitLab

 3 years ago
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

:bulb: The common way is to store the schema into a schema.bare file and provide the file path to the bare-cli. See dotnet bare --help for more information!

:warning: 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);
        }
    }
}

:bulb: The common way is to store the schema into a schema.bare file and provide the file path to the bare-cli. See dotnet 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


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK