from typing import Dict
from eventsourcing.whitehead import Event
[docs]class Upcastable(Event):
"""
For things that are upcastable.
http://code.fed.wiki.org/view/wyatt-software/view/remote-database-schema-migration
"I was sure that we could not get the schema for WyCash Plus right
on the first try. I was familiar with Smaltalk-80's object migration
mechanisms. I designed a version that could serve us in a commercial
software distribution environment.
"I chose to version each class independently and write that as a
sequential integer in the serialized versions. Objects would be
mutated to the current version on read. We supported all versions
we ever had forever.
"I recorded mutation vectors for each version to the present. These could
add, remove and reorder fields within an object. One-off mutation methods
handled the rare case where the vectors were not enough description.
"We shipped migrations to our customers with each release to be performed
on their own machines when needed without any intervention."
Ward Cunningham
"""
__class_version__ = 0
[docs] def __init__(self):
if type(self).__class_version__ > 0:
self.__dict__['__class_version__'] = type(self).__class_version__
super(Upcastable, self).__init__()
[docs] @classmethod
def __upcast_state__(cls, obj_state: Dict) -> Dict:
"""
Upcasts obj_state from the version of the class when the object
state was recorded, to be compatible with current version of the class.
"""
class_version = obj_state.get('__class_version__', 0)
while class_version < cls.__class_version__:
obj_state = cls.__upcast__(obj_state, class_version)
class_version += 1
obj_state['__class_version__'] = class_version
return obj_state
[docs] @classmethod
def __upcast__(cls, obj_state: Dict, class_version: int) -> Dict:
"""
Must be overridden in domain event classes that set class
attribute '__class_version__' to a positive value.
This method is expected to upcast obj_state from method arg
'class_version' to the next version, and return obj_state.
One style of implementation is an if-else block, in which the
conditional expressions match on the value of 'class_version'.
The implementation of this method must support upcasting each version
of the class from 0 to one less than the current version.
For example: a domain class with "__class_version__ = 1" will
need to support upcasting "class_version == 0" to version 1; and a
domain class with "__class_version__ = 2" will need to support both
upcasting "class_version == 0" to version 1, and also upcasting
"class_version == 1" to version 2.
To support backward compatibility, the recorded state of old
versions of an event class will need to be upcast to work with new
versions of the software. Commonly, this involves supplying
default values for attributes missing on old versions of events,
after an event class has been changed by adding a new attribute.
In a situation where a new version of an event class needs to be used
by existing software, it would be necessary also to support forward
compatibility. Examples of this situation include the situation
of deploying with rolling updates, and the situation where consumers
would receive and attempt to handle the new version of the event but
cannot be updated before the new event class version is deployed.
To support forward compatibility, it is necessary to restrict changes
to be merely additive: either adding new attributes to an event, or
adding new behaviours to the type of an existing attribute. Event
attributes shouldn't be removed (or renamed), existing attributes
should not have aspects of their behaviour removed, and the semantics
of an existing attribute should not change. If the type of an attribute
value is changed, the new type should substitutable for the old type.
The old software will then simply ignore new attributes, and it will
simply ignore new aspects of new types of value.
For example, the type of an attribute value can be changed to use a
subtype of the previous attribute value type. The possible range of a
value can be reduced when changing the type of an attribute value, since
all the values of the new type will be supported by the old software.
Increasing the possible range of a value will introduce values that
cannot be used by the old software, and changing the type of an
attribute value to a supertype will remove behaviour that the old
software may depend on.
Of course, if the old software doesn't in fact depend on aspects
that are removed, then those aspects can be removed without actually
breaking anything.
If backward and forward compatibility is required, and it is felt
that an event class needs to be changed that would cause an attribute
to be removed or the type of an attribute to be more general, or the
range of values of an attribute to increase, then according to Greg
Young's book 'Versioning in an Event Sourced System' it is better to
to add a new event type. However, if the old software would break
because this new event type is not supported, then supporting
forward compatibility would be elusive.
In summary, supporting forward compatibility restricts model changes to
adding attributes to existing model classes (which implies the versioned
event class' upcast method will supply default values when upcasting
the state of old versions of the event).
"""
raise NotImplementedError(cls)