class TimestampSequencedItemTestCase(ActiveRecordStrategyTestCase): EXAMPLE_EVENT_TOPIC1 = topic_from_domain_class(TimestampedEventExample1) EXAMPLE_EVENT_TOPIC2 = topic_from_domain_class(TimestampedEventExample2) def construct_positions(self): t1 = time() return t1, t1 + 0.00001, t1 + 0.00002
def test_with_timestamped_entity_event(self): # Setup the mapper, and create an event. mapper = SequencedItemMapper( sequenced_item_class=SequencedItem, sequence_id_attr_name='originator_id', position_attr_name='timestamp' ) before = time() sleep(0.000001) # Avoid test failing due to timestamp having limited precision. event2 = Event2(originator_id='entity2') sleep(0.000001) # Avoid test failing due to timestamp having limited precision. after = time() # Check to_sequenced_item() method results in a sequenced item. sequenced_item = mapper.to_sequenced_item(event2) self.assertIsInstance(sequenced_item, SequencedItem) self.assertGreater(sequenced_item.position, before) self.assertLess(sequenced_item.position, after) self.assertEqual(sequenced_item.sequence_id, 'entity2') self.assertEqual(sequenced_item.topic, topic_from_domain_class(Event2)) self.assertTrue(sequenced_item.data) # Use the returned values to create a new sequenced item. sequenced_item_copy = SequencedItem( sequence_id=sequenced_item.sequence_id, position=sequenced_item.position, topic=sequenced_item.topic, data=sequenced_item.data, ) # Check from_sequenced_item() returns an event. domain_event = mapper.from_sequenced_item(sequenced_item_copy) self.assertIsInstance(domain_event, Event2) self.assertEqual(domain_event.originator_id, event2.originator_id) self.assertEqual(domain_event.timestamp, event2.timestamp)
def test_with_versioned_entity_event(self): # Setup the mapper, and create an event. mapper = SequencedItemMapper( sequenced_item_class=SequencedItem, sequence_id_attr_name='originator_id', position_attr_name='originator_version' ) entity_id1 = uuid4() event1 = Event1(originator_id=entity_id1, originator_version=101) # Check to_sequenced_item() method results in a sequenced item. sequenced_item = mapper.to_sequenced_item(event1) self.assertIsInstance(sequenced_item, SequencedItem) self.assertEqual(sequenced_item.position, 101) self.assertEqual(sequenced_item.sequence_id, entity_id1) self.assertEqual(sequenced_item.topic, topic_from_domain_class(Event1)) self.assertTrue(sequenced_item.data) # Use the returned values to create a new sequenced item. sequenced_item_copy = SequencedItem( sequence_id=sequenced_item.sequence_id, position=sequenced_item.position, topic=sequenced_item.topic, data=sequenced_item.data, ) # Check from_sequenced_item() returns an event. domain_event = mapper.from_sequenced_item(sequenced_item_copy) self.assertIsInstance(domain_event, Event1) self.assertEqual(domain_event.originator_id, event1.originator_id) self.assertEqual(domain_event.originator_version, event1.originator_version)
def test_with_versioned_entity_event(self): # Setup the mapper, and create an event. mapper = SequencedItemMapper(sequenced_item_class=SequencedItem, sequence_id_attr_name='originator_id', position_attr_name='originator_version') entity_id1 = uuid4() event1 = Event1(originator_id=entity_id1, originator_version=101) # Check to_sequenced_item() method results in a sequenced item. sequenced_item = mapper.to_sequenced_item(event1) self.assertIsInstance(sequenced_item, SequencedItem) self.assertEqual(sequenced_item.position, 101) self.assertEqual(sequenced_item.sequence_id, entity_id1) self.assertEqual(sequenced_item.topic, topic_from_domain_class(Event1)) self.assertTrue(sequenced_item.data) # Use the returned values to create a new sequenced item. sequenced_item_copy = SequencedItem( sequence_id=sequenced_item.sequence_id, position=sequenced_item.position, topic=sequenced_item.topic, data=sequenced_item.data, ) # Check from_sequenced_item() returns an event. domain_event = mapper.from_sequenced_item(sequenced_item_copy) self.assertIsInstance(domain_event, Event1) self.assertEqual(domain_event.originator_id, event1.originator_id) self.assertEqual(domain_event.originator_version, event1.originator_version)
def take_snapshot(entity, timestamp=None): # Make the 'stored entity ID' for the entity, it is used as the Snapshot 'entity ID'. # Create the snapshot event. snapshot = Snapshot( entity_id=entity.id, timestamp=timestamp, topic=topic_from_domain_class(entity.__class__), # Todo: This should be a deepcopy. state=entity.__dict__.copy(), ) publish(snapshot) # Return the event. return snapshot
def serialize_domain_event(domain_event, json_encoder_cls=None, without_json=False, with_uuid1=False, cipher=None, always_encrypt=False): """ Serializes a domain event into a stored event. """ # assert isinstance(domain_event, DomainEvent) # Copy the state of the domain event. event_attrs = domain_event.__dict__.copy() # Get, or make, the domain event ID. if with_uuid1: event_id = event_attrs.pop('domain_event_id') else: event_id = uuid.uuid4().hex # Make stored entity ID and topic. stored_entity_id = make_stored_entity_id( id_prefix_from_event(domain_event), domain_event.entity_id) event_topic = topic_from_domain_class(type(domain_event)) # Serialise event attributes to JSON, optionally encrypted with cipher. if not without_json: if json_encoder_cls is None: json_encoder_cls = ObjectJSONEncoder event_attrs = json.dumps(event_attrs, separators=(',', ':'), sort_keys=True, cls=json_encoder_cls) if always_encrypt or domain_event.__class__.always_encrypt: if cipher is None: raise ValueError("Can't encrypt without a cipher") event_attrs = cipher.encrypt(event_attrs) # Return a named tuple. return StoredEvent( event_id=event_id, stored_entity_id=stored_entity_id, event_topic=event_topic, event_attrs=event_attrs, )
def take_snapshot(entity, at_event_id): # Make the 'stored entity ID' for the entity, it is used as the Snapshot 'entity ID'. id_prefix = id_prefix_from_entity(entity) stored_entity_id = make_stored_entity_id(id_prefix, entity.id) # Create the snapshot event. snapshot = Snapshot( entity_id=stored_entity_id, domain_event_id=at_event_id, topic=topic_from_domain_class(entity.__class__), attrs=entity.__dict__.copy(), ) publish(snapshot) # Return the event. return snapshot
def default(self, obj): if isinstance(obj, datetime.datetime): return {'ISO8601_datetime': obj.strftime('%Y-%m-%dT%H:%M:%S.%f%z')} elif isinstance(obj, datetime.date): return {'ISO8601_date': obj.isoformat()} elif isinstance(obj, UUID): return {'UUID': obj.hex} elif hasattr(obj, '__class__') and hasattr(obj, '__dict__'): topic = topic_from_domain_class(obj.__class__) state = obj.__dict__.copy() return { '__class__': { 'topic': topic, 'state': state, } } # Let the base class default method raise the TypeError. return JSONEncoder.default(self, obj)
def take_snapshot(self, entity_id, entity, last_event_version): """ Takes a snapshot by instantiating and publishing a Snapshot domain event. :rtype: Snapshot """ # Create the snapshot event. snapshot = Snapshot( originator_id=entity_id, originator_version=last_event_version, topic=topic_from_domain_class(entity.__class__), state=None if entity is None else deepcopy(entity.__dict__) ) # Publish the snapshot event. publish(snapshot) # Return the snapshot event. return snapshot
def construct_item_args(self, domain_event): """ Constructs attributes of a sequenced item from the given domain event. """ # Identify the sequence ID. sequence_id = getattr(domain_event, self.sequence_id_attr_name) # Identify the position in the sequence. position = getattr(domain_event, self.position_attr_name) # Construct the topic from the event class. topic = topic_from_domain_class(domain_event.__class__) # Serialise the state of the event. is_encrypted = self.is_encrypted(domain_event.__class__) data = self.serialize_event_attrs(domain_event.__dict__, is_encrypted=is_encrypted) other_args = tuple((getattr(domain_event, name) for name in self.other_attr_names)) return (sequence_id, position, topic, data) + other_args
def serialize_domain_event(domain_event, json_encoder_cls=None, without_json=False, with_uuid1=False, cipher=None, always_encrypt=False): """ Serializes a domain event into a stored event. """ # assert isinstance(domain_event, DomainEvent) # Copy the state of the domain event. event_attrs = domain_event.__dict__.copy() # Get, or make, the domain event ID. if with_uuid1: event_id = event_attrs.pop('domain_event_id') else: event_id = uuid.uuid4().hex # Make stored entity ID and topic. stored_entity_id = make_stored_entity_id(id_prefix_from_event(domain_event), domain_event.entity_id) event_topic = topic_from_domain_class(type(domain_event)) # Serialise event attributes to JSON, optionally encrypted with cipher. if not without_json: if json_encoder_cls is None: json_encoder_cls = ObjectJSONEncoder event_attrs = json.dumps(event_attrs, separators=(',', ':'), sort_keys=True, cls=json_encoder_cls) if always_encrypt or domain_event.__class__.always_encrypt: if cipher is None: raise ValueError("Can't encrypt without a cipher") event_attrs = cipher.encrypt(event_attrs) # Return a named tuple. return StoredEvent( event_id=event_id, stored_entity_id=stored_entity_id, event_topic=event_topic, event_attrs=event_attrs, )
def test_recreate_domain_event(self): stored_event = StoredEvent( event_id='1', stored_entity_id='entity1', event_topic='eventsourcing.domain.model.events#DomainEvent', event_attrs= '{"a":1,"b":2,"c":{"ISO8601_datetime":"2015-09-08T16:20:50.577429"},"d":{"ISO8601_datetime":"2015-09-08T16:20:50.577429+0000"},"domain_event_id":3,"e":{"ISO8601_date":"2015-09-08"},"entity_id":"entity1","entity_version":0}' ) domain_event = deserialize_domain_event( stored_event, json_decoder_cls=ObjectJSONDecoder) self.assertIsInstance(domain_event, DomainEvent) self.assertEqual('entity1', domain_event.entity_id) self.assertEqual(1, domain_event.a) self.assertEqual(2, domain_event.b) datetime_now_timezoneaware = datetime.datetime(2015, 9, 8, 16, 20, 50, 577429, tzinfo=utc_timezone) # self.assertEqual(datetime_now, domain_event.c) self.assertEqual(datetime_now_timezoneaware, domain_event.d) date_now = datetime.date(2015, 9, 8) self.assertEqual(date_now, domain_event.e) self.assertEqual(3, domain_event.domain_event_id) # Check the TypeError is raised. stored_event = StoredEvent( event_id='1', stored_entity_id='entity1', event_topic=topic_from_domain_class(NotADomainEvent), event_attrs= '{"a":1,"b":2,"stored_entity_id":"entity1","timestamp":3}') self.assertRaises(ValueError, deserialize_domain_event, stored_event, json_decoder_cls=ObjectJSONDecoder)
def test_recreate_domain_event(self): stored_event = StoredEvent(event_id='1', stored_entity_id='entity1', event_topic='eventsourcing.domain.model.events#DomainEvent', event_attrs='{"a":1,"b":2,"c":{"ISO8601_datetime":"2015-09-08T16:20:50.577429"},"d":{"ISO8601_datetime":"2015-09-08T16:20:50.577429+0000"},"domain_event_id":3,"e":{"ISO8601_date":"2015-09-08"},"entity_id":"entity1","entity_version":0}') domain_event = deserialize_domain_event(stored_event, json_decoder_cls=ObjectJSONDecoder) self.assertIsInstance(domain_event, DomainEvent) self.assertEqual('entity1', domain_event.entity_id) self.assertEqual(1, domain_event.a) self.assertEqual(2, domain_event.b) datetime_now_timezoneaware = datetime.datetime(2015, 9, 8, 16, 20, 50, 577429, tzinfo=utc_timezone) # self.assertEqual(datetime_now, domain_event.c) self.assertEqual(datetime_now_timezoneaware, domain_event.d) date_now = datetime.date(2015, 9, 8) self.assertEqual(date_now, domain_event.e) self.assertEqual(3, domain_event.domain_event_id) # Check the TypeError is raised. stored_event = StoredEvent(event_id='1', stored_entity_id='entity1', event_topic=topic_from_domain_class(NotADomainEvent), event_attrs='{"a":1,"b":2,"stored_entity_id":"entity1","timestamp":3}') self.assertRaises(ValueError, deserialize_domain_event, stored_event, json_decoder_cls=ObjectJSONDecoder)
def test_with_timestamped_entity_event(self): # Setup the mapper, and create an event. mapper = SequencedItemMapper(sequenced_item_class=SequencedItem, sequence_id_attr_name='originator_id', position_attr_name='timestamp') before = time() sleep( 0.000001 ) # Avoid test failing due to timestamp having limited precision. event2 = Event2(originator_id='entity2') sleep( 0.000001 ) # Avoid test failing due to timestamp having limited precision. after = time() # Check to_sequenced_item() method results in a sequenced item. sequenced_item = mapper.to_sequenced_item(event2) self.assertIsInstance(sequenced_item, SequencedItem) self.assertGreater(sequenced_item.position, before) self.assertLess(sequenced_item.position, after) self.assertEqual(sequenced_item.sequence_id, 'entity2') self.assertEqual(sequenced_item.topic, topic_from_domain_class(Event2)) self.assertTrue(sequenced_item.data) # Use the returned values to create a new sequenced item. sequenced_item_copy = SequencedItem( sequence_id=sequenced_item.sequence_id, position=sequenced_item.position, topic=sequenced_item.topic, data=sequenced_item.data, ) # Check from_sequenced_item() returns an event. domain_event = mapper.from_sequenced_item(sequenced_item_copy) self.assertIsInstance(domain_event, Event2) self.assertEqual(domain_event.originator_id, event2.originator_id) self.assertEqual(domain_event.timestamp, event2.timestamp)
def topic_from_domain_class(self, domain_class): return topic_from_domain_class(domain_class)
class IntegerSequencedItemTestCase(ActiveRecordStrategyTestCase): EXAMPLE_EVENT_TOPIC1 = topic_from_domain_class(ExampleVersionEntityEvent1) EXAMPLE_EVENT_TOPIC2 = topic_from_domain_class(ExampleVersionEntityEvent2) def construct_positions(self): return 0, 1, 2