When constructing “enterprise” systems it often turns out that ValueObjects (or case classes) are created. They store information about some instance of entity that is processed by the system. For example:

case class Person(name: String, address: Address)

This way of data representation in the system has two major strengths:


And drawbacks:


We want to implement a framework that will allow creating new “classes” (types, types’ constructors, objects of the new types) incrementally using our own “bricks”. Using our own “bricks” production we can reach the following strengths:


In order to construct a new compound type we should clarify how a common class is arranged. In Person class declaration the following components can be distinguished:


When using Person class and its features, the following operation can be distinguished:

“The first class” entity is Person class. Its features are the entities of “the second class”. They are not objects and we can’t operate abstractedly with them.

What we want is to make features self-contained entities of the “first class” and design a new class of them.

Declare name feature:

trait SlotId[T]

case class SlotIdImpl[T](slotId:String, ...) extends SlotId[T]

def slot[T](slotId:String, ...) = SlotIdImpl[T](slotId, ...)

val name = slot[String]("name", ...)

Such declaration exposes the feature itself irrelative of the entity this feature will be used in. Meta information can either be in an obvious way tied to the feature identifier (using the external map), or indicated in the object that represents the feature. Data processing is a bit simplified in the last variant, though enhancement by new types of meta information is complicated.

Slot Sequence


In order to get a new type we should build several features into an ordered list. In order to construct a type, aggregated from other types, we’ll use the same method as in HList type (for example, from a great shapeless library)

sealed trait SlotSeq {
   type ValueType <: HList
}
case object SNil extends SlotSeq {
   type ValueType = HNil
}
case class ::[H<:SlotId, T<:SlotSeq](head:H, tail : T) extends SlotSeq {
   type ValueType = H :: T#ValueType
}

As you can see, when constructing a feature list we are also constructing a value type (ValueType), which is compatible with the feature list.

Feature Grouping


Features can be used as they are just by creating a complete collection of all possible features. But it’s better to organize them into “clusters”. They are feature sets that relate to the same object class/type.

object PersonType {
  val name = slot[String]("name", ...)
  val address ...
  ...
}

Such grouping can be also made with the help of traits. This allows us to declare same features in different “clusters”

trait Identifiable {
  val id = slot[Long]("id")
}

object Employee extends Identifiable

Besides, “clusters” enable us to automatically add a wrapper object to the features of meta information. This can be quite useful when processing the data on the basis of meta information.

Instance Representation


The data referring to an entity can be represented in two main forms: Map or RecordSet. Map contains feature-value pairs, while RecordSet contains an ordered list of features and an array of values that are arranged in the same order. RecordSet allows to economically represent the data about a great number of instances, while Map allows to create “a thing in itself” – a self-contained object, which contains all meta information with features values. These two methods can be both used in parallel depending on the current requirements.

In order to represent RecordSet lines typified HList can be used (for example, from shapeless library). During the ordered slot sequence building form a compliant HList type.

type ValueType = head.Type :: tail.ValueType

In order to create a strictly typified Map, we’ll need to use our own SlotValue class instead of Entry class.
case class SlotValue[T](slot:SlotId[T], value:T)

Besides feature name and value, it also contains generic value type. This allows us to guarantee at the stage of compilation that the feature will get a compatible type value. Map will require a separate implementation. In the easiest case SlotValue list can be used. It’s automatically converted to the general Map when it’s necessary.

Summary


Besides the mentioned above basic data and types structures there are also useful helper functions that are based on the basic toolset


Such framework can be applied when processing polymorphic data on the basis of meta information about the features, foe example:


By using the meta information presentation facility all aspects of data processing can be described in details without using annotations.

[described constructions on github]
[original source]

Subscribe to Kukuruku Hub

Or subscribe with RSS

0 comments