We are wondering how to design a DQL-Data-Schema, which is able to store versioned data.
We must be able to each write a Query…
which only returns the just the current values
which returns the values of a specific Version (Timetravel)
which returns all of the history values and the current values. So I can see how the value has changed over time. (like a git-history-view)
We must version following:
Scalar Types (integer, string, float, etc.)
List of Scalars
List of Relationships
Every version must contain the Information:
User (String) Who did it?
Date (DateTime) When did it happen?
Message (String) Why did it happen?
What we also want =>
1 Mutation == 1 Version (which could contain several changes) like a git-commit which contains several changes
For easier Understanding, we can have a look at following dql example schema (which has no versioning right now):
Type Train {
Horsepower: int
OutsideColor: String
Wagon: [uid] """ a list of wagons connected to the Train """
}
Type Wagon {
CurrentLoad: int
MaxLoad: int
}
We not only want to version the properties (like Horsepower) but also the Relationship between Train and Wagon. As you can imagine a Train is connected to different Wagons over Time. We like to save which wagon was connected to which train over time. We want to be able to query which train had which wagons with which properties at a specific time/version.
Please give us a suggestion how to solve that versioning most efficient with the DQL Schema and DQL Query Language?
Many different ways you could do this each having pros and cons based upon your exact use case.
Here is one way. I wrote this up with a GraphQL Schema as it makes it simpler to see IMO. DQL schema would be generated from this GraphQL schema with type and interface dotted predicates.
interface Train {
id: ID
Horsepower: Int
OutsideColor: String
Wagons: [WagonWithHistory]
}
interface History {
byUser: User!
changedOn: DateTime!
reason: String!
}
type TrainHistory implements Train & History {
ref: Train! @hasInverse(field: "History")
}
type TrainWithHistory implements Train {
History: [TrainHistory] @hasInverse(field: "ref")
}
interface Wagon {
id: ID
CurrentLoad: Int
MaxLoad: Int
}
type WagonHistory implements Wagon & History {
ref: Wagon! @hasInverse(field: "History")
}
type WagonWithHistory implements Wagon {
History: [WagonHistory] @hasInverse(field: "ref")
}
type User {
id: ID
username: String! @id
}
query getTrain($id: ID) {
getTrainWithHistory(id: $id) {
id
Horsepower
OutsideColor
Wagons {
id
CurrentLoad
MaxLoad
}
}
}
This depends if you know exactly what version ID you want, of if you only know the version timestamp you want.
query getTrainBefore($id: ID, $before: DateTime, $wagonHistory: Boolean = true) {
getTrainWithHistory(id: $id) {
id
History(filter: { changedOn: { le: $before } } order: { desc: changedOn } first: 1) {
# could have no History
byUser {
username
}
changedOn
reason
Horsepower
OutsideColor
Wagons {
id
CurrentLoad @skip(if: $wagonHistory)
MaxLoad @skip(if: $wagonHistory)
History(filter: { changedOn: { le: $before } } order: { desc: changedOn } first: 1) @include(if: $wagonHistory) {
# could have no History
byUser {
username
}
changedOn
reason
CurrentLoad
MaxLoad
}
}
}
}
}
fragment HistoryDetails on History {
byUser {
username
}
changedOn
reason
}
fragment WagonDetails on WagonWithHistory {
id
CurrentLoad
MaxLoad
History(order: { desc: changedOn }) {
...HistoryDetails
CurrentLoad
MaxLoad
}
}
fragment TrainDetails on {
Horsepower
OutsideColor
Wagons {
...WagonDetails
}
}
query getTrainBefore($id: ID) {
getTrainWithHistory(id: $id) {
id
...TrainDetails
History(order: { desc: changedOn }) {
...HistoryDetails
...TrainDetails
}
}
}
Working with this schema doing mutations gets tricky, but not impossible. With DQL you can do variable blocks and upserts to get the current version, create a node copying the current version, and then updating what you want to the new version. That is more than I wish to code out here as a proof of concept example, but it is all possible
This is all feasible with the schema suggested above.