Chronologische Daten
Bestimmte Attribute erfordern eine zeitabhängige Speicherung von Werten. Beispiel: Preis eines Buchs. Dieser Preis hat zu unterschiedlichen Zeitpunkten unterschiedliche Werte. In der Geschäftslogik sollen neben dem aktuellen Wert auch die Werte der Vergangenheit bzw. Zukunft bekannt sein. Diese können ggf. für die Verarbeitung nötig sein.
Aus dem Attribut Preis eines Objektes wird also im Prinzip ein zeitabhängiges Attribute Preis(t) . Dies ist (natürlich) in C# so nicht umsetzbar. Hilfsweise wird die hier vorgestellte Bibliothek eingesetzt.
Desing
Grundvarianten
Folgende Varianten stehen im Framework zur Verfügung (Namensraum Schleupen.CS.PI.Framework.Chronology.Collections):
- TimeBoxedValueCollection
- Lücken erlaubt
- mehrere Werte zur gleichen Zeit erlaubt
- TimeBoxedValueSet
- Lücken erlaubt
- nur max. ein Wert zur gleichen Zeit erlaubt
- TimedValueRay
- keine Lücken erlaubt
- nur max. ein Wert zur gleichen Zeit erlaubt
Operationen auf Zeitscheiben
Alle Operationen auf diese Werten laufen nach folgendem Verfahren ab:
- Split
- Act
- Melt
Die folgenden Beispiele zeigen, wie man mehrere Attribute, die den Zustand des umhüllenden Objekts ausmachen, arbeitet. Somit können für das umhüllende Objekt die unterschiedlichen Zustände zu den unterschiedlichen Zeitpunkten verwendet werden.
Aufgabe:
- Split
Trenne alle Intervalle an den Zeitpunkten auf, an denen mindestens eine Intervallgrenze existiert: - Act
Auf diesen Intervallen konstanter Attribute können jegliche Operationen zeitunabhängig durchgeführt werden: - Melt
Verschmelze solche Nachbar-Intervalle, in denen alle Attribute gleich sind, zu einem Intervall:
Automatische Optimierung
Benachbarte zeitlich begrenzte Werte werden in allen Collections beim Einfügen automatisch zusammengefasst. Ist der eigentliche Wert gleich, so werden benachbarte Intervalle also zu einem den Gesamtraum umfassenden Intervall verschmolzen.
Intervalltyp
Der Intervalltyp gibt an, ob geschlosse oder rechts-offene Intervalle verwendet werden. Bei einen Geschäftsjahr ist es z.B. sinnvoll geschlossene Intervalle zu verwenden, damit in der UI und der Datenbank z.B. 2014-01-01 bis 2014-12-31 zu sehen ist anstelle von 2014-01-01 bis 2015-01-01. Die zweite Darstellung könnte durch den Anwender als schwer verständlich empfunden werden. Welcher Intervalltyp zu verwenden ist, hängt vom jeweiligen Kontext ab und ist eine fachliche Entscheidung.
Die Intervalle sind dabei aus Konsistenzgründen auf allen Ebenen gleich, das UI sollte also dieselben Intervalle anzeigen wie diese in der Datenbank abgelegt werden.
Intervallerzeugung
- Es können Intervalle von einem Datum bis zu einem anderen Datum erzeugt werden.
DateTimeOffset from = new DateTimeOffset(2016, 1, 1, 0, 0, 0, new TimeSpan(0, 0, 0)); DateTimeOffset to = new DateTimeOffset(2016, 12, 31, 0, 0, 0, new TimeSpan(0, 0, 0)); TimeBox timeBox = new TimeBox(from, to);
- Es können Intervalle rechts oder links unbeschränkte Intervalle erzeugt werden.
DateTimeOffset from = new DateTimeOffset(2016, 1, 1, 0, 0, 0, new TimeSpan(0, 0, 0)); TimeBox rightUnboundTimeBox = TimeBox.CreateRightUnbound(from); DateTime to = new DateTimeOffset(2016, 12, 31, 0, 0, 0, new TimeSpan(0, 0, 0)); TimeBox leftUnboundTimeBox = TimeBox.CreateLeftUnbound(to);
Genauigkeit
Die Genauigkeit gibt an, welche Zeitpunkte als identisch betrachtet werden. Bei einer Genauigkeit Tag bezeichnen somit die Werte 2014-07-17 00:00:00 und 2014-07-17 12:15:00 denselben Zeitpunkt. Folgende Genauigkeiten stehen zur Verfügung:
- Year
- Day
- Month
- Hour
- Minute
- Second
- None (anwendendes Land muss Genauigkeit sicherstellen, es wird auf maximale Genauigkeit verglichen)
Bei geschlossenen Intervallen enden angrenzende Intervalle automatisch immer an einem durch die Granularität definierten vorigen Zeitpunkt.
Rundung
Alle Zeitpunkte werden durch die Framework Klassen (außer bei Granularität None) automatisch gerundet, so dass diese in Datenbank und Domäne immer konsistent sind.
KDM/KSM
Im KDM stehen zur weiteren Verwendung in den KSM Diensten spezialisierte Datentypen bereit, die damit ausdrücken können, ob ein Service die Daten als offenes oder geschlossenes Intervall liefert/erwartet.
Die folgenden Beispiele zeigen das Verhalten der verschiedenen Collection-Typen bei hinzufügen neuer Werte.
TimeBoxedValueCollection
Hier wird der Wert 2 hinzugefügt. Eine Optimierung ist hier nicht nötig, da alle Werte unterschiedlich sind und mehrere Werte je Zeitraum existieren können.
[---1---[ [---3---[ + [---2---[ =>[---1---[ [---3---[ [---2---[
TimeBoxedValueSet
Durch das Hinzufügen des Wertes 3 werden Teile der existierenden Werte überschrieben.
[---1---[ [---2----[ + [----3----[ =>[-1-[[----3----[[--2--[
Durch das Hinzufügen des zweiten Wertes 1 findet eine Optimierung statt.
[---1---[ [---2---[ + [---1---[ =>[-----1-----[[---2---[
TimedValueRay
Hier wird der Wert 4 mit Add hinzugefügt. Dieser überschreibt die existierenden Werte im selben Zeitraum.
[---1---[[----2----[[---3--- + [----4----- =>[---1---[[-2--[[---4------
Hier wird der Wert 4 mit Insert hinzugefügt. Dieser fügt den Wert zwischen die existierenden Werte hinzu - Endzeitpunkt wird von der Startzeitpunkt des nächsten zeitlich begrenzten Werts übernommen.
[---1---[[----2----[[---3--- + [----4----- =>[---1---[[-2--[[-4-[[---3--- -----
Weitere Beispiele sind in den Unittests der Assembly Core.Tests im Namensraum Chronology zu finden.
Chronologische Beziehungen zwischen Aggregaten
Analog zu den Collections zum Arbeiten mit chronologisierten Value Types können auch Beziehungen zwischen Aggregaten chronologisiert werden. Hierzu werden die Collections TimeBoxedAssociationCollection
, TimeBoxedAssociationSet
und TimedAssociationRay
mit gleicher Semantik genutzt.
Diese Art der Chronologisierung ist nur beim Aggregate Boundary Muster von Bedeutung. Falls die Aggregate per Id verknüpft sind, kann die Id einfach als Value Type chronologisiert werden.
Falls bei der Arbeit mit einer chronologischen Aggregatbeziehung neue Einträge entstehen (z.B. ein bestehender Zeitstreifen wird zerteilt), wird im Gegensatz zu Value Types bei der Chronologisierung von Aggregatbeziehungen keine Kopie von bestehenden Entitäten erzeugt, sondern eine Referenz auf das gleiche Aggregate Boundary.
Chronologische Eltern-Kind Beziehungen
Analog zu den Collections zum Arbeiten mit chronologisierten Value Types können auch Eltern-Kind Beziehungen innerhalb von Aggregaten chronologisiert werden. Hierzu werden die Collections TimeBoxedChildEntityCollection, TimeBoxedChildEntitySet und TimedChildEntityRay mit gleicher Semantik genutzt.
Falls bei der Arbeit mit einer chronologischen Eltern-Kindbeziehung neue Einträge entstehen (z.B. ein bestehender Zeitstreifen wird zerteilt), wird wie bei Values Types eine Kopie des Kindelementes erzeugt, die Verweise zeigen also auf unterschiedliche Objekte mit gleichen Daten. Kind-Elemente implementieren hierfür das Interface IClonableEntity
.
Es ist fachlich zu entscheiden, ob in der Clone()
Methode eine tiefe oder flache Kopie des zu Objektes erstellt wird.
Migration TimedValueRay to TimeChildEntityRay
Um TimedValueRay in TimeChildEntityRay zu migrieren sind die folgenden Schritte notwendig:
- Ableitung von
TimedValue
auf Ableitung vonTimedChildEntity
ändern TEntity
vonstruct
aufclass
ändern undIAggregateChild
implementierenTEntity
muss zusätzlichIClonableEntity
,IEquatable
implementieren- Der Konstruktor von
TEntity
muss eine ParentId entgegen nehmen
Setzen von ParentId auf TimedValueRay
Um die ParentId auf einem TimedValueRay
zu setzen, darf der Value kein primitiver Datentyp sein. Man kann allerdings selbst einen struct
definieren, der die ParentId entgegen nimmt.
Ab PI 3.19 kann hier ein Fabrikmethode zur Erzeugung des Objekts angegeben werden, sodass man hier einfach eingreifen kann. Hierüber kann auch die Id gesetzt werden!
Abgrenzung
Dies ist nicht mit einer Historie der Werte zu verwechseln! Bei chronologischen Daten werden die Werte zu mehreren Zeitpunkten gleichzeitig für die Verarbeitung benötigt. Diese Bibliothek ist kein Ersatz für ein Änderungsprotokoll.