Skip to content

Core EAV

The Entity-Attribute-Value (EAV) framework is the data model underlying every business concept in the trading platform: orders, positions, scenarios, contracts, accounts. Every "thing" in the system is an entity with named attributes whose values are produced by pluggable providers.

The core types

classDiagram
  class IEntity~V, I~ {
    <<interface>>
    +Name: string
    +Identifier: I
    +Attributes: IAttribute~V~[]
  }
  class IAttribute~V~ {
    <<interface>>
    +Name: string
    +Type: Type
  }
  class IValueProvider~V, I~ {
    <<interface>>
    +GetValue(entity: IEntity~V,I~) V?
  }
  class IRegistry {
    <<interface>>
    +Register(entity)
    +AttachValueProvider(entityId, attrName, provider)
    +GetValue~V~(entityId) V?
  }
  class Registry {
    -entities: ConcurrentDictionary
    -providers: ConcurrentDictionary
  }

  IEntity --> IAttribute
  IAttribute --> IValueProvider
  IValueProvider --> IRegistry
  Registry ..|> IRegistry

IEntity<V, I>

Represents a typed entity. V is the value type, I is the identifier type. An entity has a name, an identifier, and a list of attributes.

public interface IEntity<V, I> : IEvent
    where V : struct
    where I : IEntityIdentifier
{
    string Name { get; }
    I Identifier { get; }
    IEnumerable<IAttribute<V>> Attributes { get; }
}

IAttribute<V>

A named, typed attribute. The Type property returns the CLR type that the value provider produces.

public interface IAttribute<V> : IEvent
    where V : struct
{
    string Name { get; }
    Type Type { get; }
}

IValueProvider<V, I>

A function from entity → optional value. Value providers are pluggable — an attribute's value can come from a static config, a market data feed, or a derived computation.

public interface IValueProvider<V, I>
    where V : struct
    where I : IEntityIdentifier
{
    V? GetValue(IEntity<V, I> entity);
}

IRegistry (and the concrete Registry)

The global registry. Services register entities and value providers at startup; consumers query the registry for current values.

public interface IRegistry
{
    void Register<V, I>(IEntity<V, I> entity) where V : struct where I : IEntityIdentifier;
    void AttachValueProvider<V, I>(IIdentifier id, string attributeName, IValueProvider<V, I> provider) where V : struct where I : IEntityIdentifier;
    V? GetValue<V>(IIdentifier id) where V : struct;
    IEnumerable<V> GetAllValues<V>(IIdentifier id) where V : struct;
}

Usage

using Virtufin.Core;

var registry = new Registry();

// Register an entity: "BTC price"
var entityId = new EntityIdentifier<MarketEntity>("btc-price");
var entity = new MarketEntity(entityId, "BTC Spot Price", new[]
{
    new Attribute<DecimalAmount>("last-trade"),
    new Attribute<DecimalAmount>("24h-volume"),
});
registry.Register(entity);

// Attach value providers
registry.AttachValueProvider(entityId, "last-trade", () => currentBtcPrice);
registry.AttachValueProvider(entityId, "24h-volume", () => current24hVolume);

// Query
var lastTrade = registry.GetValue<DecimalAmount>(entityId);

Why EAV?

The trading platform's domain has many "kinds of things" (orders, positions, scenarios, contracts, accounts, ...). A traditional ORM would model each as a separate class. The EAV model instead:

  • Unifies the access pattern: every "thing" is an entity, looked up by name.
  • Allows dynamic schemas: new attributes can be added without changing the class hierarchy.
  • Enables attribute-level caching: the registry caches per-attribute, per-entity values; if a last-trade value hasn't changed, downstream consumers don't recompute.
  • Decouples computation from declaration: a value provider can be a static config today and a market data feed tomorrow, with no change to the entity declaration.

The cost is that the CLR can't statically enforce that "an order has a quantity attribute" — that's a runtime check. We accept this trade-off for the flexibility.

Generics summary

Type Constraints Purpose
IEntity<V, I> V : struct, I : IEntityIdentifier A typed entity
IAttribute<V> V : struct A named, typed attribute
IValueProvider<V, I> V : struct, I : IEntityIdentifier A function from entity → value
IRegistry (none) Global registry interface
Registry (none) Concrete in-memory registry

V is the value type, I is the identifier type — same convention as the rest of the library.