Logo FsShelter

More about topology schema

FsShelter will serialize most data types you might want to use in the definition of the DU case for the topology streams. There are however several things to keep in mind...

Grouping expressions

FsShelter DSL uses F# lambdas to define grouping expressions. Since this has to be transformed into a list of fields Storm can understand, only a simple projection of properties is supported, so don't expect to be able to call any functions.

Examples of structural nesting supported:

type Number<'t> = { X:'t; Desc:string }
type ComplexNumber = { Re:float; Im:float }

type RecordSchema = 
    | Original of int
    | Described of Number<int>
    | Translated of Number<int> option
    | Complex of Number<ComplexNumber>

Here is how FsShelter will present the schema to Storm when declaring stream/outputs:

And when referenced in the grouping expressions, FsShelter will translate the fields back to these names:

open FsShelter.DSL
#nowarn "25" / // for stream matching expressions
// group tuples on Original stream, the `int` value is directly accessible
let ex1 = Group.by (function Original x -> x)
// group tuples on Described stream, flattened to [X] for Storm
let ex2 = Group.by (function Described x -> x.X) 
// group tuples on Translated stream, not flattened - the `X` and `Desc` fields will not be accessible to Storm
let ex3 = Group.by (function Translated x -> x)
// group tuples on Complex stream, flattened to [X] for Storm, `X.Re` is not accessible
let ex4 = Group.by (function Complex x -> x.X)

As you can see this flattening of records is done only one layer deep. Also keep in mind that while use of collection types for fields is possible it doesn't provde any meaningful way to express that to Storm. In summary:

Implementation details and performance considerations

FsShelter will serialize most schemata exactly as you would imagine, but there are always "interesting" scenarios. Here are some of the implementation details to keep in mind (the serializers below are provided in the FsShelter.Multilang package):

System and other arbitrary streams names

Certain features of Storm are available through special streams, like "__tick". Since DU case name like that would look very wrong in an F# application, FsShelter checks for DisplayName attribute that facilitates the mapping, for example:

type TimedSchema = 
    | [<System.ComponentModel.DisplayName("__tick")>] Tick

Will use __tick as the name for the stream, while keeping F# conventions in place for the DU case.

namespace System
namespace FsShelter
type Number<'t> = { X: 't Desc: string }
't
Number.X: 'a
Multiple items
val string: value: 'T -> string

--------------------
type string = String
type ComplexNumber = { Re: float Im: float }
Multiple items
val float: value: 'T -> float (requires member op_Explicit)

--------------------
type float = Double

--------------------
type float<'Measure> = float
type RecordSchema = | Original of int | Described of Number<int> | Translated of Number<int> option | Complex of Number<ComplexNumber>
Multiple items
val int: value: 'T -> int (requires member op_Explicit)

--------------------
type int = int32

--------------------
type int<'Measure> = int
type 'T option = Option<'T>
module DSL from FsShelter
<summary> Embedded DSL for defining the topologies </summary>
val ex1: (bool -> Topology.ComponentId -> Topology.ComponentId -> Topology.Stream<RecordSchema>)
type Group = static member by: select: Expr<('t -> 'p)> -> (bool -> ComponentId -> ComponentId -> Stream<'t>)
<summary> define fields grouping </summary>
static member Group.by: select: Quotations.Expr<('t -> 'p)> -> (bool -> Topology.ComponentId -> Topology.ComponentId -> Topology.Stream<'t>)
union case RecordSchema.Original: int -> RecordSchema
val x: int
val ex2: (bool -> Topology.ComponentId -> Topology.ComponentId -> Topology.Stream<RecordSchema>)
union case RecordSchema.Described: Number<int> -> RecordSchema
val x: Number<int>
Number.X: int
val ex3: (bool -> Topology.ComponentId -> Topology.ComponentId -> Topology.Stream<RecordSchema>)
union case RecordSchema.Translated: Number<int> option -> RecordSchema
val x: Number<int> option
val ex4: (bool -> Topology.ComponentId -> Topology.ComponentId -> Topology.Stream<RecordSchema>)
union case RecordSchema.Complex: Number<ComplexNumber> -> RecordSchema
val x: Number<ComplexNumber>
Number.X: ComplexNumber
type TimedSchema = | Tick
namespace System.ComponentModel
Multiple items
type DisplayNameAttribute = inherit Attribute new: unit -> unit + 1 overload member Equals: obj: obj -> bool member GetHashCode: unit -> int member IsDefaultAttribute: unit -> bool static val Default: DisplayNameAttribute member DisplayName: string
<summary>Specifies the display name for a property, event, or public void method that takes no arguments.</summary>

--------------------
ComponentModel.DisplayNameAttribute() : ComponentModel.DisplayNameAttribute
ComponentModel.DisplayNameAttribute(displayName: string) : ComponentModel.DisplayNameAttribute

Type something to start searching.