Skip to content
This repository has been archived by the owner on Apr 13, 2019. It is now read-only.

data containers

Konstantin Sobolev edited this page Jul 25, 2017 · 12 revisions

This needs updates!

Intro

Data containers are run-time incarnations of schema types. Their implementations should exist for every target programming language. These classes support marshalling into one of over-the-wire representations and unmarshalling back.

In many cases more than one implementation of data containers will be provided.

Raw containers

Raw containers are generic implementations that don't depend on particular schema. They provide minimal type safety but a lot of run-time checks, they're mostly suitable for general-purpose framework and library code. Examples:

trait RawRecordData(val schema: RecordSchema, val meta: Option[RawData]) {
  def get(field: Field): RawData
  def get(field: Field, schema: Schema): RawData
  def set(field: Field, schema: Schema, value: RawData): Unit
}

trait RawMapData(val schema: MapSchema, val meta: Option[RawData]) {
  def get(key: RawData): RawData
  def get(key: RawData, schema: Schema): RawData
  def set(key: RawData, schema: Schema, value: RawData): Unit
}

Notice how there are pairs of get functions: one gives the default representation, another one takes a specific model to return.

Type safe containers

Type safe containers can come in different flavors with different degrees of type safety, depending on the implementation language. In many cases they are generated basing on the schema. For example they can look like this:

trait FileRecord {
  def name: string
  def name_=(name: string): Unit
  def owner: UserId
  def owner_=(owner: UserId):Unit
  def getOwner(schema: Schema[User]): User
  def setOwner(schema: Schema[User], owner: User): Unit
}

Typically that's what client code will be dealing with.

Value containers

Value containers such as record fields, union tags, map values or list entries are all structured in the same way. There are several nested layers of information:

  1. At the innermost layer there is actual data container such as RawRecordData or StringPrimitive
  2. There is an Either[Error,Data] wrapper around it. We support fine-grained errors, and any part of the data tree can hold an error.
  3. Then there is a Future around it, because federator can be asked to resolve any part of the tree asynchronously
  4. Finally there is MultiValue which represents an instance of MultiType. This is a map from representation types to corresponding values. For non-pivotable fields it will contain only one entry

Graphically:

Plus any data instance can have meta-data attached to it, which happens at the innermost level.