# Entity versioning
- [Entity versioning](#entity-versioning)
- [What is entity versioning](#what-is-entity-versioning)
- [Entities](#entities)
- [Relations](#relations)
- [Fields](#fields)
- [Getting started](#getting-started)
- [Install the app](#install-the-app)
- [Define entities](#define-entities)
- [Create versioned models](#create-versioned-models)
- [Create entity versions](#create-entity-versions)
- [Config](#config)
- [Versioning `Nodes` and `Entities`](#versioning-nodes-and-entities)
- [Entity definition](#entity-definition)
- [Versioning registry](#versioning-registry)
- [Versioning handler](#versioning-handler)
- [Versioning instance factory](#versioning-instance-factory)
- [Versioning model factory](#versioning-model-factory)
## What is entity versioning
To understand what entity versioning is supposed to be, first we need to define what an entity is.
### Entities
Tipicaly a sql modelation consists of multiple models which are connected by foreign keys. It looks like a **graph**. An entity is just a [DAG](https://en.wikipedia.org/wiki/Directed_acyclic_graph) based on the **nodes** of a **sub graph** of the models graph.
If you look the models on your app as a graph, an entity is _like_ a sub graph of it. It has a root defined by a model and a subset of it fields and relations.
Basically, with the following models:
```python
class Student(models.Model):
name = models.CharField(max_length=64)
age = models.PositiveIntegerField()
class Teacher(models.Model):
name = models.CharField(max_length=64)
class Course(models.Model):
name = models.CharField(max_length=64)
teacher = models.ForeignKey(
Teacher, on_delete=models.PROTECT, related_name="courses"
)
students = models.ManyToManyField(Student, related_name="courses")
```
#### Relations
In this example, the graph here would look like:
>[Student] <--> [Course] <--> [Teacher]
Some of the sub-graphs (entities) that can be constructed from here would be:
1.
```
[Course]
|--> [Teacher]
|--> [Student]
```
2.
```
[Course] --> [Teacher]
```
3.
```
[Student] --> [Course] --> [Teacher]
```
4.
```
[Student] --> [Course]
|--> [Teacher]
|--> [Student]
```
>Notice that the `Students` are repeated, but it is acyclic since each represents different nodes in the graph.
And what your imagination can build.
#### Fields
When you define an entity **node**, which is based on a model, you need to define which fields from that model belong to the entity node.
Following the example above, you could have this as entities:
1.
```
[Course]
|--> name
|--> [Teacher]
```
Or this,
2.
```
[Course]
|--> [Student]
|--> [Teacher]
```
And so on...
## Getting started
### Install the app
Fisrt we, need to install the app. Currently the app is distributed within `notgraphql`, so first you need to [install the dependency](https://github.com/thenotcompany/notgraphql#installation-gear).
Then, add the app cofig to your installed apps:
```python
INSTALLED_APPS = [
...
"notgraphql.entity_versioning.apps.EntityVersioningConfig",
]
```
### Define entities
Define an entity on the `models.entities` (see [config sections](#config)) directory. The entities are defined on `JSON` files like the following one:
```json
{
"app_label": "your_app",
"name": "Course",
"root_model_name": "Course"
"entity": {
"_versioned_model_name": "CourseVersion",
"name": null,
"teacher": {
"_versioned_model_name": "CourseTeacherVersion",
"name": null
},
"students": {
"_versioned_model_name": "CourseStudentVersion",
"name": null
}
}
}
```
### Create versioned models
Once your entities are defined, go ahead and create the versioned models with the provided command:
```bash
python manage.py create_versioned_models
```
The versioned models are created into a `.py` file in the `models.entities` (see [config sections](#config)) directory.
The following file with the models is going to be created:
```python
from django.db import models
from notgraphql.entity_versioning.models import BaseModel
class CourseVersion(BaseModel):
name = models.CharField(max_length=64)
teacher = models.ForeignKey(
"CourseTeacherVersion",
on_delete=models.PROTECT,
related_name="courses",
)
students = models.ManyToManyField(
"CourseStudentVersion",
related_name="courses",
)
concrete_course = models.ForeignKey(
"Course",
on_delete=models.SET_NULL,
related_name="course_entity_versions",
null=True,
)
class CourseTeacherVersion(BaseModel):
name = models.CharField(max_length=64)
root_node = models.ForeignKey(
"CourseVersion",
on_delete=models.CASCADE,
related_name="course_teacher_versions",
)
class CourseStudentVersion(BaseModel):
name = models.CharField(max_length=64)
root_node = models.ForeignKey(
"CourseVersion",
on_delete=models.CASCADE,
related_name="course_student_versions",
)
```
Then import your models into your models module and create/execute the migrations.
```bash
python manage.py makemigrations
python manage.py migrate
```
### Create entity versions
With the previous setting steps, you are ready to start storing your entities versions.
To generate versions a `VersioningHandler` is provided. The main idea of the handler is identify changes made to the defined entities by providing instances, the handler identifies the root nodes of the provided instances.
```python
from notgraphql.entity_versioning.handler import VersioningHandler
teacher = Teacher.objects.create(name="Tomas")
course = Course.objects.create(name="Entity versioning", teacher=teacher)
# Instantiate the handler
handler = VersioningHandler()
# Register the changes
handler.register_changes(Teacher, [teacher])
handler.register_changes(Course, [course])
# For this particular case, it is actually not necesary to register both
# changes since they belong to the same entity instance.
# Create the entity versions
handler.version_changed_entities()
```
The previous code is going to be equivalent to:
```python
versioned_course = CourseVersion.objects.create(
name="Entity versioning",
concrete_course=course,
)
versioned_teacher = CourseTeacherVersion.objects.create(
name="Tomas",
root_node=versioned_course,
)
versioned_course.teacher = versioned_teacher
```
Then some new changes can be done to the entity instance:
```python
# Create a student
student = Student.objects.create(name="Joaquin", age=27)
# Instantiate a new handler and register the changes
handler = VersioningHandler()
handler.register_changes(Student, [student])
handler.version_changed_entities()
```
Which is going to be equivalent to:
```python
versioned_course = CourseVersion.objects.create(
name="Entity versioning",
concrete_course=course,
)
versioned_teacher = CourseTeacherVersion.objects.create(
name="Tomas",
root_node=versioned_course,
)
versioned_student = CourseStudentVersion.objects.create(
name="Joaquin",
root_node=versioned_course,
)
versioned_course.teacher = versioned_teacher
versioned_course.students.add(versioned_student)
```
## Configuration
Configurations for the app can be provided within a dictionary on the `ENTITY_VERSIONING` settings variable.
Available configuration options are:
- Entitty definition directory: The directory where the entity definitions are placed. The value must be a path to a sub module of the apps.
- Key: `entity_sources`
- Default value: `models.entities`
- Entity versioned models directory: The directory where the versioned models are going to be created to. The value must be a path to a sub module of the apps.
- Key: `entity_versioned_models`
- Default value: `models.entities`
Example settings:
```python
ENTITY_VERSIONING = {
"entity_sources": "path.to.module.within.the.app",
"entity_versioned_models": "path.to.module.within.the.app",
}
```
## Versioning `Nodes` and `Entities`
To interact with the models, fields, relations and other Django elements, a base [`Node`](https://github.com/thenotcompany/notgraphql/blob/feat/versioning/notgraphql/entity_versioning/entities/nodes.py#L68) class is provided to abstract the relations whithin models. Each reference to a model on the defined entities is going to be mapped into a different node. Meaning that a model tracked on different entities or the same model tarcked more than once in the same entity is going to have a different nodes.
### `Node`
The `Node` class provides an interface to interact with the node model along with its relations and also some methods to facilitate the interaction with the versioned models. Also, the following methods are provided:
- [`build_optimized_queryset`](https://github.com/thenotcompany/notgraphql/blob/feat/versioning/notgraphql/entity_versioning/entities/nodes.py#L206): get an optimized queryset for the node with its related child nodes prefetched. This is done to minimize the amount of queries done to the database to get the full entity data.
The optimized queryset is obtained recursively with the related nodes.
To see the all the methods and properties available, check out the [`Node`](https://github.com/thenotcompany/notgraphql/blob/feat/versioning/notgraphql/entity_versioning/entities/nodes.py#L68) class.
### `VersioningNode`
On top of the base nodes, the [`VersioningNode`](https://github.com/thenotcompany/notgraphql/blob/feat/versioning/notgraphql/entity_versioning/entities/entities.py#L27) is defined to add the versioning relevant information like the fields and relations that are tracked on the entity.
### `VersioningEntity`
Then the [`VersioningEntity`](https://github.com/thenotcompany/notgraphql/blob/feat/versioning/notgraphql/entity_versioning/entities/entities.py#L76) class is defined to instatiate the versioned entity nodes with the entity definitions. Each entity definitions is used to instantiate the a versioning entity class with its nodes.
## Entity definition
To define an entity we use a `JSON` file. Every entity needs to live in its own file, to explain it we are going to base in this example:
```json
{
"name": "EntityName",
"app_label": "your_app",
"root_model_name": "EntityRootModelName",
"entity": {
"_versioned_model_name": "RootVersionedModelName",
"some_field_name": null,
"field_name_of_related_model": {
"_versioned_model_name": "RelatedVersionedModelName",
"some_other_field_name": null
},
"other_field_name_of_related_model": {
"_versioned_model_name": "AnotherRelatedVersionedModelName",
"some_field_name": null
}
}
}
```
Explanation for the json keys:
- **name**: An entity has a name that identifies the entity. It must be unique among other entities.
- **app_label**: It should be the entity root node (root model) `app label`.
- **root_model_name**: Entity root model name (remember that this model should live in the given `app_label`).
- **entity**: Here is where the entity definition starts. The first node makes reference to the root model defined erlier.
- **entity._versioned_model_name**: The name for the versioned model that will keep track of this model and hold it instances versions.
- **entity.----**: Any model field that the entity should keep track of, there are 2 types of fields:
- **Value fields**: Fields which hold values (CharFields, Int, etc..) for which case the value needs to be setted as `null` in the entity definition. See (_some_field_name_, _some_other_field_name_ as examples).
- **Relation fields**: Filds which hold relations to other models (ForeignKeyField, OneToOneField or ManyToManyField), in which case the value should be another javascript object with `_versioned_model_name` and its fields to version.
See the [Config](#config) section to know where to place this entities definitions.
## Versioning registry
To load all the applications entities, we use a **registry**. A registry is just a class that has the hability to go through your files and identify all your `JSON` defined entities based on your [configuration settings](#config), and load them as `entities` with `nodes`, see [Versioning `Nodes` and `Entities`](#versioning-nodes-and-entities).
>You don't need to load the entities manually because it is automatically being done on the `ready` function of the `EntityVersioningConfig`, our custom [`AppConfig`](https://docs.djangoproject.com/en/4.1/ref/applications/#django.apps.AppConfig). Check the [Config](#config) section for a better understunding.
Any way, the [Versioning registry](notgraphql/entity_versioning/registry.py) does provide a couple of functionalities which again, you shouldn't use directly, but could be worth to understund. These functions are exposed for you to be used indirectly through the [Versioning handler](#versioning-handler), so feel free to skip to that part if you only wish to understund better how to version entities.
The 2 functions exposed by the versioning registry are:
1. [`get_root_nodes`](https://github.com/thenotcompany/notgraphql/blob/feat/versioning/notgraphql/entity_versioning/registry.py#L61): Function that receives a model and a list of instances from that model and returns all the instances `root nodes` from the different entities in which those instances might belong.
2. [`version_entities`](https://github.com/thenotcompany/notgraphql/blob/feat/versioning/notgraphql/entity_versioning/registry.py#L84): Function that receives a list of root node instances from one or more entities and versions them all.
## Versioning handler
The [versioning handler](https://github.com/thenotcompany/notgraphql/blob/feat/versioning/notgraphql/entity_versioning/handler.py#L8) is the class that you should interact with in order to version your entities.
This class provides an API to store changed instances root nodes and to versioned those changed entities. In the background it does so by interacting with the functions described in the [Versioning registry](#versioning-registry) section.
To work with the versioning handler, you need to `initialize` the handler and interact with its very small API:
1. [`register_changes`](https://github.com/thenotcompany/notgraphql/blob/feat/versioning/notgraphql/entity_versioning/handler.py#L17): Function that receives a model and a list of instances from that model and stores all the instances `root nodes` from the different entities in which those instances might belong. It interacts with the versioning registry `get_root_nodes` method.
2. [`version_changed_entities`](https://github.com/thenotcompany/notgraphql/blob/feat/versioning/notgraphql/entity_versioning/handler.py#L30): Method to version all stored root node instances on the versioning handler.
So in order to version anything with the handler, you need to have register changes before. Check the [Create entity versions](#create-entity-versions) section to understand how it should be used.
## Versioning instance factory
The [VersioningInstanceFactory](https://github.com/thenotcompany/notgraphql/blob/feat/versioning/notgraphql/entity_versioning/entities/versioning_instances_factory.py#L14) is a class built on top of the [VersioningEntity](#VersioningEntity). It extends the `VersioningEntity` with methods to create the entity versions from the respective root node instances.
In order to do so, it interacts directly with the [VersioningInstanceFactoryNode](https://github.com/thenotcompany/notgraphql/blob/feat/versioning/notgraphql/entity_versioning/entities/versioning_instances_factory.py#L14), which is nothing else than an extension of the [VersioningNode](#VersioningNode).
The `VersioningInstanceFactoryNode` provides all the versioning logic for an entity. From the class docstrings:
>Each node is in charge of creating its own instances of the versioned
model, while also being in charge of propagating the creation of related
nodes versions. Then, the version creation starts from the root node of
and entity and its propagated through the nodes calling the
`create_entity_versions` method.
It does so with the only public method (on top of the `VersioningNode` extension), [`create_entity_versions`](https://github.com/thenotcompany/notgraphql/blob/feat/versioning/notgraphql/entity_versioning/entities/versioning_instances_factory.py#L42). It is an optimize algorithm to minimize the database queries to version multiple instances from an entity.
The [Versioning registry](#versioning-registry) then, interacts with the `VersioningInstanceFactory` to create its entity versions. This factory exposes an API where its main function to focus on, is [`version_entity`](https://github.com/thenotcompany/notgraphql/blob/feat/versioning/notgraphql/entity_versioning/entities/versioning_instances_factory.py#L337).
## Versioning model factory
The [VersioningModelFactory](https://github.com/thenotcompany/notgraphql/blob/feat/versioning/notgraphql/entity_versioning/entities/versioning_models_factory.py#L157) is a class built on top of the [VersioningEntity](#VersioningEntity). It extends the `VersioningEntity` with methods to create the versioned models from the entity definitions.
In order to do so, it interacts directly with the [VersioningModelFactoryNode](https://github.com/thenotcompany/notgraphql/blob/feat/versioning/notgraphql/entity_versioning/entities/versioning_models_factory.py#L16), which extends the [VersioningNode](#VersioningNode).
The `VersioningInstanceFactoryNode` provides all the logic to translate the nodes/model into their Django model definitions. Its enhanced with methods to obtain the static code that defines the versioned model.
## NotgraphQL integration
> TODO: See related [PR](https://github.com/thenotcompany/notgraphql/pull/281).