patternpythonMinor
Event sourcing with Python
Viewed 0 times
withsourcingpythonevent
Problem
I've been studying the event sourcing pattern and trying to come up with a set of classes or minimal library I can share across projects.
Below you can find the implementation of this main parts of this pattern including the EventStore and Repository. I'd be thankful on getting a review on whether this is what the pattern is about and advice on where I could improve.
Note that I excluded classes
```
# -- coding: utf-8 --
from abc import ABCMeta, abstractmethod
from datetime import *
from . import Immutable, Identifiable, Aggregate, Repository, snake_case, \
ConventionBasedMessageHandler, ConventionBasedMessageBus, Serializer
class EventSource(Identifiable):
def __init__(self):
Identifiable.__init__(self)
self.version = 0
self._recorded_events = []
def get_version(self):
return self.version
def get_recorded_events(self):
return self._recorded_events
def clear_recorded_events(self):
self._recorded_events = []
def _record(self, event):
self._apply(event)
self._recorded_events.append(event)
_record_that = _record_event = _record # sugar alias
def _apply(self, event):
method_name = '_apply_' + snake_case(event.__class__.__name__)
method = getattr(self, method_name, None)
if not method or not callable(method):
raise Exception('no %s method defined in class' % (method_name))
method(event)
def load_from_history(self, events):
"""reconstitutes state of this object based on its history of events"""
for event in events:
self._apply(event)
self.version = len(events)
reconstitute_from_history = load_from_history # sugar alias
class AggregateWithEventSourcing(Aggregate, EventSource):
def __init__(self):
Aggregate.__init__(self)
Below you can find the implementation of this main parts of this pattern including the EventStore and Repository. I'd be thankful on getting a review on whether this is what the pattern is about and advice on where I could improve.
Note that I excluded classes
Immutable, Identifiable and Aggregate for brevity but I hope their nouns are self-describing enough to provide the necessary context.```
# -- coding: utf-8 --
from abc import ABCMeta, abstractmethod
from datetime import *
from . import Immutable, Identifiable, Aggregate, Repository, snake_case, \
ConventionBasedMessageHandler, ConventionBasedMessageBus, Serializer
class EventSource(Identifiable):
def __init__(self):
Identifiable.__init__(self)
self.version = 0
self._recorded_events = []
def get_version(self):
return self.version
def get_recorded_events(self):
return self._recorded_events
def clear_recorded_events(self):
self._recorded_events = []
def _record(self, event):
self._apply(event)
self._recorded_events.append(event)
_record_that = _record_event = _record # sugar alias
def _apply(self, event):
method_name = '_apply_' + snake_case(event.__class__.__name__)
method = getattr(self, method_name, None)
if not method or not callable(method):
raise Exception('no %s method defined in class' % (method_name))
method(event)
def load_from_history(self, events):
"""reconstitutes state of this object based on its history of events"""
for event in events:
self._apply(event)
self.version = len(events)
reconstitute_from_history = load_from_history # sugar alias
class AggregateWithEventSourcing(Aggregate, EventSource):
def __init__(self):
Aggregate.__init__(self)
Solution
I started to review your code. Look
Additional remaks:
TL;TR.
In summary:
I think you design your application to be object oriented (probably from UML or Java). It's good, but in Python you can also use functional programing.
# review: comments bellow:# Is `Identifiable` base class useful? Does it add something signifiant?
class EventSource(object):
def __init__(self):
# review: use `super` call here: that way you can change your base class
super(EventSource, self).__init__()
# review: add the `self.id` field here.
self.id = None
self.version = 0
self._recorded_events = []
# review: `get_version()` is useless since `self.version` is public
# def get_version(self):
# return self.version
def get_recorded_events(self):
return self._recorded_events
def clear_recorded_events(self):
# review: if you want to "clear" a list, do it as follow.
# That way, any reference to `self._recorded_events` won't be broken.
self._recorded_events[:] = []
def _record(self, event):
self._apply(event)
self._recorded_events.append(event)
# review: No need to add "sugar alias" for "protected" methods.
# _record_that = _record_event = _record # sugar alias
# review: It's more natural to raise `AttributeError` instead of `Exception`.
def _apply(self, event):
method_name = '_apply_' + snake_case(event.__class__.__name__)
method = getattr(self, method_name) # review: raise `AttributeError` if missing.
method(event) # review: raise `TypeError` if not callable
def load_from_history(self, events):
"""reconstitutes state of this object based on its history of events"""
for event in events:
self._apply(event)
self.version = len(events)
# review: Is it really usefull
reconstitute_from_history = load_from_history # sugar aliasAdditional remaks:
get_recorded_events()method returnsself._recorded_events, which is a mutable object (a Pythonlist), So, the end-user can modify it. If it can, drop this method and turnself._recorded_eventsto a "public" attribute:self.recorded_events.
self.idis not used in this class.
- What is the role of
self.version?
TL;TR.
In summary:
- Try to be more Pythonic,
- ABC/metaclasses are not useful,
- There are too many one-line methods: consider refactoring,
- The class hierarchy is unnecessary too complex,
- You can use functions (really).
I think you design your application to be object oriented (probably from UML or Java). It's good, but in Python you can also use functional programing.
Code Snippets
# Is `Identifiable` base class useful? Does it add something signifiant?
class EventSource(object):
def __init__(self):
# review: use `super` call here: that way you can change your base class
super(EventSource, self).__init__()
# review: add the `self.id` field here.
self.id = None
self.version = 0
self._recorded_events = []
# review: `get_version()` is useless since `self.version` is public
# def get_version(self):
# return self.version
def get_recorded_events(self):
return self._recorded_events
def clear_recorded_events(self):
# review: if you want to "clear" a list, do it as follow.
# That way, any reference to `self._recorded_events` won't be broken.
self._recorded_events[:] = []
def _record(self, event):
self._apply(event)
self._recorded_events.append(event)
# review: No need to add "sugar alias" for "protected" methods.
# _record_that = _record_event = _record # sugar alias
# review: It's more natural to raise `AttributeError` instead of `Exception`.
def _apply(self, event):
method_name = '_apply_' + snake_case(event.__class__.__name__)
method = getattr(self, method_name) # review: raise `AttributeError` if missing.
method(event) # review: raise `TypeError` if not callable
def load_from_history(self, events):
"""reconstitutes state of this object based on its history of events"""
for event in events:
self._apply(event)
self.version = len(events)
# review: Is it really usefull
reconstitute_from_history = load_from_history # sugar aliasContext
StackExchange Code Review Q#147033, answer score: 3
Revisions (0)
No revisions yet.