class ExampleApplicationWithTimeuuidSequencedItems(object):
    def __init__(self):
        self.event_store = EventStore(
            record_manager=CassandraRecordManager(
                record_class=TimeuuidSequencedRecord,
                sequenced_item_class=SequencedItem,
            ),
            sequenced_item_mapper=SequencedItemMapper(
                sequenced_item_class=SequencedItem,
                sequence_id_attr_name='originator_id',
                position_attr_name='event_id',
            )
        )
        self.repository = EventSourcedRepository(
            event_store=self.event_store,
        )
        self.persistence_policy = PersistencePolicy(
            event_store=self.event_store,
            event_type=DomainEvent,
        )

    def start_entity(self):
        return ExampleEntity.start()

    def close(self):
        self.persistence_policy.close()

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.close()
Ejemplo n.º 2
0
class ExampleApplicationWithExtendedSequencedItemType(object):
    def __init__(self, session):
        self.event_store = EventStore(
            record_manager=SQLAlchemyRecordManager(
                session=session,
                record_class=ExtendedIntegerSequencedRecord,
                sequenced_item_class=ExtendedSequencedItem,
            ),
            sequenced_item_mapper=ExtendedSequencedItemMapper(
                sequenced_item_class=ExtendedSequencedItem,
                sequence_id_attr_name='originator_id',
                position_attr_name='originator_version',
                other_attr_names=('timestamp', ),
            ))
        self.repository = ExampleRepository(event_store=self.event_store, )
        self.persistence_policy = PersistencePolicy(
            event_store=self.event_store,
            persist_event_type=DomainEvent,
        )

    def close(self):
        self.persistence_policy.close()

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.close()
class TestPersistencePolicy(unittest.TestCase):
    def setUp(self):
        self.event_store = mock.Mock(spec=AbstractEventStore)
        self.persistence_policy = PersistencePolicy(
            event_store=self.event_store,
            persist_event_type=VersionedEntity.Event
        )

    def tearDown(self):
        self.persistence_policy.close()

    def test_published_events_are_appended_to_event_store(self):
        # Check the event store's append method has NOT been called.
        assert isinstance(self.event_store, AbstractEventStore)
        self.assertEqual(0, self.event_store.store.call_count)

        # Publish a versioned entity event.
        entity_id = uuid4()
        domain_event1 = VersionedEntity.Event(
            originator_id=entity_id,
            originator_version=0,
        )
        publish(domain_event1)

        # Check the append method has been called once with the domain event.
        self.event_store.store.assert_called_once_with(domain_event1)

        # Publish a timestamped entity event (should be ignored).
        domain_event2 = TimestampedEntity.Event(
            originator_id=entity_id,
        )
        publish(domain_event2)

        # Check the append() has still only been called once with the first domain event.
        self.event_store.store.assert_called_once_with(domain_event1)
class TestPersistencePolicy(unittest.TestCase):
    def setUp(self):
        self.event_store = mock.Mock(spec=AbstractEventStore)
        self.persistence_policy = PersistencePolicy(
            event_store=self.event_store,
            event_type=VersionedEntity.Event
        )

    def tearDown(self):
        self.persistence_policy.close()

    def test_published_events_are_appended_to_event_store(self):
        # Check the event store's append method has NOT been called.
        assert isinstance(self.event_store, AbstractEventStore)
        self.assertEqual(0, self.event_store.append.call_count)

        # Publish a versioned entity event.
        entity_id = uuid4()
        domain_event1 = VersionedEntity.Event(originator_id=entity_id, originator_version=0)
        publish(domain_event1)

        # Check the append method has been called once with the domain event.
        self.event_store.append.assert_called_once_with(domain_event1)

        # Publish a timestamped entity event (should be ignored).
        domain_event2 = TimestampedEntity.Event(originator_id=entity_id)
        publish(domain_event2)

        # Check the append() has still only been called once with the first domain event.
        self.event_store.append.assert_called_once_with(domain_event1)
class ExampleApplicationWithTimeuuidSequencedItems(object):
    def __init__(self):
        self.event_store = EventStore(
            active_record_strategy=CassandraActiveRecordStrategy(
                active_record_class=CqlTimeuuidSequencedItem,
                sequenced_item_class=SequencedItem,
            ),
            sequenced_item_mapper=SequencedItemMapper(
                sequenced_item_class=SequencedItem,
                sequence_id_attr_name='originator_id',
                position_attr_name='event_id',
            )
        )
        self.repository = EventSourcedRepository(
            mutator=ExampleEntity._mutate,
            event_store=self.event_store,
        )
        self.persistence_policy = PersistencePolicy(self.event_store)

    def start_entity(self):
        return ExampleEntity.start()

    def close(self):
        self.persistence_policy.close()

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.close()
class ExampleApplicationWithAlternativeSequencedItemType(object):
    def __init__(self, session):
        self.event_store = EventStore(
            active_record_strategy=SQLAlchemyActiveRecordStrategy(
                session=session,
                active_record_class=StoredEventRecord,
                sequenced_item_class=StoredEvent,
            ),
            sequenced_item_mapper=SequencedItemMapper(
                sequenced_item_class=StoredEvent,
                sequence_id_attr_name='originator_id',
                position_attr_name='originator_version',
            )
        )
        self.repository = ExampleRepository(
            event_store=self.event_store,
        )
        self.persistence_policy = PersistencePolicy(self.event_store)

    def close(self):
        self.persistence_policy.close()

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.close()
Ejemplo n.º 7
0
class ExampleApplicationWithAlternativeSequencedItemType(object):
    def __init__(self, session):
        self.event_store = EventStore(
            record_manager=SQLAlchemyRecordManager(
                session=session,
                record_class=StoredEventRecord,
                sequenced_item_class=StoredEvent,
            ),
            event_mapper=SequencedItemMapper(
                sequenced_item_class=StoredEvent,
                sequence_id_attr_name="originator_id",
                position_attr_name="originator_version",
            ),
        )
        self.repository = ExampleRepository(event_store=self.event_store)
        self.persistence_policy = PersistencePolicy(self.event_store)

    def close(self):
        self.persistence_policy.close()

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.close()
Ejemplo n.º 8
0
class SnapshottingApplication(SimpleApplication):
    def __init__(self, period=10, snapshot_record_class=None, **kwargs):
        self.period = period
        self.snapshot_record_class = snapshot_record_class
        super(SnapshottingApplication, self).__init__(**kwargs)

    def setup_event_store(self):
        super(SnapshottingApplication, self).setup_event_store()
        # Setup snapshot store, using datastore session, and SnapshotRecord class.
        # Todo: Refactor this into a new create_sqlalchemy_snapshotstore() function.
        self.snapshot_store = EventStore(
            SQLAlchemyRecordManager(
                session=self.datastore.session,
                record_class=self.snapshot_record_class or SnapshotRecord
            ),
            SequencedItemMapper(
                sequence_id_attr_name='originator_id',
                position_attr_name='originator_version'
            )
        )

    def setup_repository(self, **kwargs):
        # Setup repository with a snapshot strategy.
        self.snapshot_strategy = EventSourcedSnapshotStrategy(
            event_store=self.snapshot_store
        )
        super(SnapshottingApplication, self).setup_repository(
            snapshot_strategy=self.snapshot_strategy, **kwargs
        )

    def setup_persistence_policy(self, persist_event_type):
        persist_event_type = persist_event_type or DomainEntity.Event
        super(SnapshottingApplication, self).setup_persistence_policy(persist_event_type)
        self.snapshotting_policy = SnapshottingPolicy(self.repository, self.period)
        self.snapshot_persistence_policy = PersistencePolicy(
            event_store=self.snapshot_store,
            event_type=Snapshot
        )

    def setup_table(self):
        super(SnapshottingApplication, self).setup_table()
        # Also setup snapshot table.
        self.datastore.setup_table(self.snapshot_store.record_manager.record_class)

    def close(self):
        super(SnapshottingApplication, self).close()
        self.snapshotting_policy.close()
        self.snapshot_persistence_policy.close()
Ejemplo n.º 9
0
class ExampleDDDApplication(Generic[TEntity]):
    def __init__(self, datastore):
        event_store = EventStore(
            record_manager=SQLAlchemyRecordManager(
                session=datastore.session, record_class=IntegerSequencedNoIDRecord
            ),
            event_mapper=SequencedItemMapper(
                sequence_id_attr_name="originator_id",
                position_attr_name="originator_version",
            ),
        )
        # Todo: Remove having two repositories, because they are identical.
        self.aggregate1_repository = AggregateRepository(event_store=event_store)
        self.aggregate2_repository = AggregateRepository(event_store=event_store)
        self.persistence_policy = PersistencePolicy(
            persist_event_type=ExampleAggregateRoot.Event, event_store=event_store
        )

    def create_aggregate1(self) -> Aggregate1:
        """
        Factory method, creates and returns a new aggregate1 root entity.
        """
        a: Aggregate1 = Aggregate1.__create__()
        return a

    def create_aggregate2(self)-> Aggregate2:
        """
        Factory method, creates and returns a new aggregate1 root entity.
        """
        a: Aggregate2 = Aggregate2.__create__()
        return a

    def create_aggregate3(self)-> Aggregate3:
        """
        Factory method, creates and returns a new aggregate1 root entity.
        """
        a = Aggregate3.__create__()
        return a

    def close(self):
        self.persistence_policy.close()

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.close()
Ejemplo n.º 10
0
class ExampleDDDApplication(object):
    def __init__(self, datastore):
        event_store = EventStore(
            record_manager=SQLAlchemyRecordManager(
                session=datastore.session,
                record_class=IntegerSequencedNoIDRecord,
            ),
            sequenced_item_mapper=SequencedItemMapper(
                sequence_id_attr_name='originator_id',
                position_attr_name='originator_version',
            )
        )
        self.aggregate1_repository = AggregateRepository(
            event_store=event_store,
        )
        self.aggregate2_repository = AggregateRepository(
            event_store=event_store,
        )
        self.persistence_policy = PersistencePolicy(
            event_type=ExampleAggregateRoot.Event,
            event_store=event_store,
        )

    def create_aggregate1(self):
        """
        Factory method, creates and returns a new aggregate1 root entity.

        :rtype: Aggregate1
        """
        return Aggregate1.__create__()

    def create_aggregate2(self):
        """
        Factory method, creates and returns a new aggregate1 root entity.

        :rtype: Aggregate2
        """
        return Aggregate2.__create__()

    def close(self):
        self.persistence_policy.close()

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.close()
Ejemplo n.º 11
0
class ExampleApplicationWithAlternativeSequencedItemType(object):
    def __init__(self):
        self.event_store = EventStore(
            record_manager=CassandraRecordManager(
                record_class=StoredEventRecord,
                sequenced_item_class=StoredEvent),
            event_mapper=SequencedItemMapper(sequenced_item_class=StoredEvent,
                                             other_attr_names=()),
        )
        self.repository = ExampleRepository(event_store=self.event_store)
        self.persistence_policy = PersistencePolicy(
            event_store=self.event_store, persist_event_type=DomainEvent)

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.persistence_policy.close()
Ejemplo n.º 12
0
class ExampleApplicationWithAlternativeSequencedItemType(object):
    def __init__(self):
        self.event_store = EventStore(
            active_record_strategy=CassandraActiveRecordStrategy(
                active_record_class=StoredEventRecord,
                sequenced_item_class=StoredEvent,
            ),
            sequenced_item_mapper=SequencedItemMapper(
                sequenced_item_class=StoredEvent,
                other_attr_names=(),
            ))
        self.repository = ExampleRepository(event_store=self.event_store, )
        self.persistence_policy = PersistencePolicy(self.event_store)

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.persistence_policy.close()
class ExampleDDDApplication(object):
    def __init__(self, datastore):
        event_store = EventStore(
            active_record_strategy=SQLAlchemyActiveRecordStrategy(
                session=datastore.session,
                active_record_class=IntegerSequencedItemRecord,
            ),
            sequenced_item_mapper=SequencedItemMapper(
                sequence_id_attr_name='originator_id',
                position_attr_name='originator_version',
            )
        )
        self.aggregate_repository = EventSourcedRepository(
            mutator=ExampleAggregateRoot._mutate,
            event_store=event_store,
        )
        self.persistence_policy = PersistencePolicy(
            event_type=ExampleAggregateRoot.Event,
            event_store=event_store,
        )

    def create_example_aggregate(self):
        """
        Factory method, creates and returns a new example aggregate root object.

        :rtype: ExampleAggregateRoot 
        """
        event = ExampleAggregateRoot.Created(originator_id=uuid.uuid4())
        aggregate = ExampleAggregateRoot._mutate(initial=None, event=event)
        aggregate._pending_events.append(event)
        return aggregate

    def close(self):
        self.persistence_policy.close()

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.close()
Ejemplo n.º 14
0
class ExampleDDDApplication(object):
    def __init__(self, datastore):
        event_store = EventStore(
            active_record_strategy=SQLAlchemyActiveRecordStrategy(
                session=datastore.session,
                active_record_class=IntegerSequencedItemRecord,
            ),
            sequenced_item_mapper=SequencedItemMapper(
                sequence_id_attr_name='originator_id',
                position_attr_name='originator_version',
            ))
        self.aggregate_repository = EventSourcedRepository(
            mutator=ExampleAggregateRoot._mutate,
            event_store=event_store,
        )
        self.persistence_policy = PersistencePolicy(
            event_type=ExampleAggregateRoot.Event,
            event_store=event_store,
        )

    def create_example_aggregate(self):
        """
        Factory method, creates and returns a new example aggregate root object.

        :rtype: ExampleAggregateRoot 
        """
        event = ExampleAggregateRoot.Created(originator_id=uuid.uuid4())
        aggregate = ExampleAggregateRoot._mutate(initial=None, event=event)
        aggregate._pending_events.append(event)
        return aggregate

    def close(self):
        self.persistence_policy.close()

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.close()
Ejemplo n.º 15
0
class WithEventPersistence(WithRecordManagers):
    """
    Base class for test cases that need persistence policies.
    """
    def setUp(self):
        super(WithEventPersistence, self).setUp()
        # Setup the persistence subscriber.
        self.entity_event_store = EventStore(
            record_manager=self.entity_record_manager,
            event_mapper=SequencedItemMapper(
                sequenced_item_class=SequencedItem,
                sequence_id_attr_name="originator_id",
                position_attr_name="originator_version",
            ),
        )
        self.log_event_store = EventStore(
            record_manager=self.log_record_manager,
            event_mapper=SequencedItemMapper(
                sequenced_item_class=SequencedItem,
                sequence_id_attr_name="originator_id",
                position_attr_name="timestamp",
            ),
        )
        self.snapshot_store = EventStore(
            record_manager=self.snapshot_record_manager,
            event_mapper=SequencedItemMapper(
                sequenced_item_class=SequencedItem,
                sequence_id_attr_name="originator_id",
                position_attr_name="originator_version",
            ),
        )

        self.integer_sequenced_event_policy = None
        if self.entity_event_store is not None:
            self.integer_sequenced_event_policy = PersistencePolicy(
                event_store=self.entity_event_store,
                persist_event_type=VersionedEntity.Event,
            )

        self.timestamp_sequenced_event_policy = None
        if self.log_event_store is not None:
            self.timestamp_sequenced_event_policy = PersistencePolicy(
                event_store=self.log_event_store,
                persist_event_type=LoggedEvent)

        self.snapshot_policy = None
        if self.snapshot_store is not None:
            self.snapshot_policy = PersistencePolicy(
                event_store=self.snapshot_store, persist_event_type=Snapshot)

    def tearDown(self):
        # Close the persistence subscriber.
        if self.snapshot_policy:
            self.snapshot_policy.close()
        if self.timestamp_sequenced_event_policy:
            self.timestamp_sequenced_event_policy.close()
        if self.entity_event_store:
            self.integer_sequenced_event_policy.close()
        super(WithEventPersistence, self).tearDown()
class ExampleApplicationWithAlternativeSequencedItemType(object):
    def __init__(self):
        self.event_store = EventStore(
            active_record_strategy=CassandraActiveRecordStrategy(
                active_record_class=StoredEventRecord,
                sequenced_item_class=StoredEvent,
            ),
            sequenced_item_mapper=SequencedItemMapper(
                sequenced_item_class=StoredEvent,
                other_attr_names=(),
            )
        )
        self.repository = ExampleRepository(
            event_store=self.event_store,
        )
        self.persistence_policy = PersistencePolicy(self.event_store)

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.persistence_policy.close()
Ejemplo n.º 17
0
class ExampleApplicationWithExtendedSequencedItemType(object):
    def __init__(self, datastore):
        self.event_store = EventStore(
            active_record_strategy=SQLAlchemyActiveRecordStrategy(
                datastore=datastore,
                active_record_class=SqlExtendedIntegerSequencedItem,
                sequenced_item_class=ExtendedSequencedItem,
            ),
            sequenced_item_mapper=ExtendedSequencedItemMapper(
                sequenced_item_class=ExtendedSequencedItem,
                event_sequence_id_attr='entity_id',
                event_position_attr='entity_version',
            ))
        self.repository = ExampleRepository(event_store=self.event_store, )
        self.persistence_policy = PersistencePolicy(self.event_store)

    def close(self):
        self.persistence_policy.close()

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.close()
Ejemplo n.º 18
0
class WithPersistencePolicies(WithActiveRecordStrategies):
    """
    Base class for test cases that need persistence policies.
    """
    def setUp(self):
        super(WithPersistencePolicies, self).setUp()
        # Setup the persistence subscriber.
        self.entity_event_store = EventStore(
            active_record_strategy=self.entity_active_record_strategy,
            sequenced_item_mapper=SequencedItemMapper(
                sequenced_item_class=SequencedItem,
                sequence_id_attr_name='originator_id',
                position_attr_name='originator_version'))
        self.log_event_store = EventStore(
            active_record_strategy=self.log_active_record_strategy,
            sequenced_item_mapper=SequencedItemMapper(
                sequenced_item_class=SequencedItem,
                sequence_id_attr_name='originator_id',
                position_attr_name='timestamp'))
        self.snapshot_store = EventStore(
            active_record_strategy=self.snapshot_active_record_strategy,
            sequenced_item_mapper=SequencedItemMapper(
                sequenced_item_class=SequencedItem,
                sequence_id_attr_name='originator_id',
                position_attr_name='originator_version'))

        self.integer_sequenced_event_policy = None
        if self.entity_event_store is not None:
            self.integer_sequenced_event_policy = PersistencePolicy(
                event_store=self.entity_event_store,
                event_type=VersionedEntity.Event,
            )

        self.timestamp_sequenced_event_policy = None
        if self.log_event_store is not None:
            self.timestamp_sequenced_event_policy = PersistencePolicy(
                event_store=self.log_event_store,
                event_type=Logged,
            )

        self.snapshot_policy = None
        if self.snapshot_store is not None:
            self.snapshot_policy = PersistencePolicy(
                event_store=self.snapshot_store,
                event_type=Snapshot,
            )

    def tearDown(self):
        # Close the persistence subscriber.
        if self.snapshot_policy:
            self.snapshot_policy.close()
        if self.timestamp_sequenced_event_policy:
            self.timestamp_sequenced_event_policy.close()
        if self.entity_event_store:
            self.integer_sequenced_event_policy.close()
        super(WithPersistencePolicies, self).tearDown()
Ejemplo n.º 19
0
class SimpleApplication(Pipeable, Generic[TVersionedEntity, TVersionedEvent]):
    """
    Base class for event sourced applications.

    Constructs infrastructure objects such as the repository and
    event store, and also the notification log which presents the
    application state as a sequence of events.

    Needs actual infrastructure classes.
    """

    infrastructure_factory_class: Type[
        InfrastructureFactory] = InfrastructureFactory
    is_constructed_with_session: bool = False

    record_manager_class: Optional[Type[AbstractRecordManager]] = None
    stored_event_record_class: Optional[type] = None
    snapshot_record_class: Optional[type] = None

    sequenced_item_class: Optional[Type[NamedTuple]] = None
    sequenced_item_mapper_class: Optional[Type[SequencedItemMapper]] = None
    compressor: Any = None
    json_encoder_class: Optional[Type[JSONEncoder]] = None
    sort_keys: bool = False
    json_decoder_class: Optional[Type[JSONDecoder]] = None

    persist_event_type: Optional[PersistEventType] = None
    notification_log_section_size: Optional[int] = None
    use_cache: bool = False

    event_store_class: Type[EventStore] = EventStore
    repository_class: Type[EventSourcedRepository] = EventSourcedRepository

    use_causal_dependencies = False
    set_notification_ids = False

    def __init__(
        self,
        name: str = "",
        persistence_policy: Optional[PersistencePolicy] = None,
        persist_event_type: PersistEventType = None,
        cipher_key: Optional[str] = None,
        compressor: Any = None,
        sequenced_item_class: Optional[Type[NamedTuple]] = None,
        sequenced_item_mapper_class: Optional[
            Type[SequencedItemMapper]] = None,
        record_manager_class: Optional[Type[AbstractRecordManager]] = None,
        stored_event_record_class: Optional[type] = None,
        event_store_class: Optional[Type[EventStore]] = None,
        snapshot_record_class: Optional[type] = None,
        setup_table: bool = True,
        contiguous_record_ids: bool = True,
        pipeline_id: int = DEFAULT_PIPELINE_ID,
        json_encoder_class: Optional[Type[JSONEncoder]] = None,
        sort_keys: bool = False,
        json_decoder_class: Optional[Type[JSONDecoder]] = None,
        notification_log_section_size: Optional[int] = None,
        use_cache: bool = False,
    ):
        """
        Initialises application object.

        :param name: Name of application.
        :param persistence_policy: Persistence policy object.
        :param persist_event_type: Tuple of domain event classes to be persisted.
        :param cipher_key: Base64 unicode string cipher key.
        :param compressor: Compressor used to compress serialized event state.
        :param sequenced_item_class: Named tuple for mapping and recording events.
        :param sequenced_item_mapper_class: Object class for mapping stored events.
        :param record_manager_class: Object class for recording stored events.
        :param stored_event_record_class: Object class for event records.
        :param event_store_class: Object class uses to store and retrieve domain events.
        :param snapshot_record_class: Object class used to represent snapshots.
        :param setup_table: Option to create database tables when application starts.
        :param contiguous_record_ids: Whether or not to delegate notification ID
            generation to the record manager (to guarantee there will be no gaps).
        :param pipeline_id: ID of instance of system pipeline expressions.
        :param json_encoder_class: Object class used to encode object as JSON strings.
        :param json_decoder_class: Object class used to decode JSON strings as objects.
        :param notification_log_section_size: Number of notification items in a section.
        :param use_cache: Whether or not to keep aggregates in memory (saves replaying
            when accessing again, but uses memory).
        """
        self.name = name or type(self).create_name()

        self.notification_log_section_size = (
            notification_log_section_size
            or type(self).notification_log_section_size)

        sequenced_item_class = sequenced_item_class or type(
            self).sequenced_item_class
        sequenced_item_class = sequenced_item_class or StoredEvent  # type: ignore
        self.sequenced_item_class = sequenced_item_class
        assert self.sequenced_item_class is not None
        self.sequenced_item_mapper_class: Type[SequencedItemMapper] = (
            sequenced_item_mapper_class
            or type(self).sequenced_item_mapper_class or SequencedItemMapper)

        self.record_manager_class = (record_manager_class
                                     or type(self).record_manager_class)
        self._stored_event_record_class = stored_event_record_class
        self._snapshot_record_class = snapshot_record_class

        self.event_store_class = event_store_class or type(
            self).event_store_class

        self.json_encoder_class = json_encoder_class or type(
            self).json_encoder_class
        self.sort_keys = sort_keys or type(self).sort_keys
        self.json_decoder_class = json_decoder_class or type(
            self).json_decoder_class
        self.persist_event_type = persist_event_type or type(
            self).persist_event_type

        self.contiguous_record_ids = contiguous_record_ids
        self.pipeline_id = pipeline_id
        self._persistence_policy = persistence_policy

        self.cipher = self.construct_cipher(cipher_key)
        self.compressor = compressor or type(self).compressor

        # Default to using zlib compression when encrypting.
        if self.cipher and self.compressor is None:
            self.compressor = zlib

        self.infrastructure_factory: Optional[
            InfrastructureFactory[TVersionedEvent]] = None
        self._datastore: Optional[AbstractDatastore] = None
        self._event_store: Optional[AbstractEventStore[
            TVersionedEvent, BaseRecordManager]] = None
        self._repository: Optional[EventSourcedRepository[
            TVersionedEntity, TVersionedEvent]] = None
        self._notification_log: Optional[LocalNotificationLog] = None

        self.use_cache = use_cache or type(self).use_cache

        if (self.record_manager_class
                or self.infrastructure_factory_class.record_manager_class):

            self.construct_infrastructure()

            if setup_table:
                self.setup_table()
            self.construct_notification_log()

            if self._persistence_policy is None:
                self.construct_persistence_policy()

    @classmethod
    def create_name(cls):
        return cls.__name__.lower()

    @property
    def datastore(self) -> AbstractDatastore:
        if self._datastore is None:
            self._raise_on_missing_infrastructure("datastore")
        return self._datastore

    @property
    def event_store(
            self) -> AbstractEventStore[TVersionedEvent, BaseRecordManager]:
        if self._event_store is None:
            self._raise_on_missing_infrastructure("event_store")
        return self._event_store

    @property
    def repository(
            self) -> EventSourcedRepository[TVersionedEntity, TVersionedEvent]:
        if self._repository is None:
            self._raise_on_missing_infrastructure("repository")
        return self._repository

    @property
    def notification_log(self) -> LocalNotificationLog:
        if self._notification_log is None:
            self._raise_on_missing_infrastructure("notification_log")
        return self._notification_log

    @property
    def persistence_policy(self) -> PersistencePolicy:
        if self._persistence_policy is None:
            self._raise_on_missing_infrastructure("persistence_policy")
        return self._persistence_policy

    def _raise_on_missing_infrastructure(self, what_is_missing):
        msg = "Application class %s does not have a %s." % (
            type(self).__name__,
            what_is_missing,
        )
        if not isinstance(self, ApplicationWithConcreteInfrastructure):
            msg += (
                " and is not an ApplicationWithConcreteInfrastructure."
                " Try using or inheriting from or mixin() an application"
                " class with concrete infrastructure such as SQLAlchemyApplication"
                " or DjangoApplication or AxonApplication.")
        raise ProgrammingError(msg)

    def construct_cipher(self,
                         cipher_key_str: Optional[str]) -> Optional[AESCipher]:
        cipher_key_bytes = decode_bytes(cipher_key_str
                                        or os.getenv("CIPHER_KEY", "") or "")
        return AESCipher(cipher_key_bytes) if cipher_key_bytes else None

    def construct_infrastructure(self, *args: Any, **kwargs: Any) -> None:
        """
        Constructs infrastructure for application.
        """
        self.infrastructure_factory = self.construct_infrastructure_factory(
            *args, **kwargs)
        self.construct_datastore()
        self.construct_event_store()
        self.construct_repository()

    def construct_infrastructure_factory(
            self, *args: Any, **kwargs: Any) -> InfrastructureFactory:
        """
        Constructs infrastructure factory object.
        """
        factory_class = self.infrastructure_factory_class
        assert issubclass(factory_class, InfrastructureFactory)

        integer_sequenced_record_class = (self._stored_event_record_class
                                          or self.stored_event_record_class)
        snapshot_record_class = (self._snapshot_record_class
                                 or self.snapshot_record_class)

        return factory_class(  # type:ignore  # multiple values for keyword argument
            record_manager_class=self.record_manager_class,
            integer_sequenced_record_class=integer_sequenced_record_class,
            snapshot_record_class=snapshot_record_class,
            sequenced_item_class=self.sequenced_item_class,
            sequenced_item_mapper_class=self.sequenced_item_mapper_class,
            json_encoder_class=self.json_encoder_class,
            sort_keys=self.sort_keys,
            json_decoder_class=self.json_decoder_class,
            contiguous_record_ids=self.contiguous_record_ids,
            application_name=self.name,
            pipeline_id=self.pipeline_id,
            event_store_class=self.event_store_class,
            *args,
            **kwargs)

    def construct_datastore(self) -> None:
        """
        Constructs datastore object (which helps by creating and dropping tables).
        """
        assert self.infrastructure_factory
        self._datastore = self.infrastructure_factory.construct_datastore()

    def construct_event_store(self) -> None:
        """
        Constructs event store object.
        """
        assert self.infrastructure_factory
        factory = self.infrastructure_factory
        self._event_store = factory.construct_integer_sequenced_event_store(
            self.cipher, self.compressor)

    def construct_repository(self, **kwargs: Any) -> None:
        """
        Constructs repository object.
        """
        assert self.repository_class
        self._repository = self.repository_class(event_store=self.event_store,
                                                 use_cache=self.use_cache,
                                                 **kwargs)

    def setup_table(self) -> None:
        """
        Sets up the database table using event store's record class.
        """
        if self._datastore is not None:
            record_class = self.event_store.record_manager.record_class
            self.datastore.setup_table(record_class)

    def drop_table(self) -> None:
        """
        Drops the database table using event store's record class.
        """
        if self._datastore is not None:
            record_class = self.event_store.record_manager.record_class
            self.datastore.drop_table(record_class)

    def construct_notification_log(self) -> None:
        """
        Constructs notification log object.
        """
        self._notification_log = RecordManagerNotificationLog(
            self.event_store.record_manager,
            section_size=self.notification_log_section_size,
        )

    def construct_persistence_policy(self) -> None:
        """
        Constructs persistence policy object.
        """
        self._persistence_policy = PersistencePolicy(
            event_store=self.event_store,
            persist_event_type=self.persist_event_type)

    def change_pipeline(self, pipeline_id: int) -> None:
        """
        Switches pipeline being used by this application object.
        """
        self.pipeline_id = pipeline_id
        self.event_store.record_manager.pipeline_id = pipeline_id

    def close(self) -> None:
        # Close the persistence policy.
        if self._persistence_policy is not None:
            self._persistence_policy.close()

        # Close database connection.
        if self._datastore is not None:
            self._datastore.close_connection()

    def __enter__(self: T) -> T:
        """
        Supports use of application as context manager.
        """
        return self

    def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
        """
        Closes application when exiting context manager.
        """
        self.close()

    @classmethod
    def reset_connection_after_forking(cls) -> None:
        pass

    @classmethod
    def mixin(cls: T, infrastructure_class: type) -> T:
        """
        Returns subclass that inherits also from given infrastructure class.
        """
        return type(cls.__name__, (infrastructure_class, cls), {})

    def save(
            self,
            aggregates=(),
            orm_objects_pending_save=(),
            orm_objects_pending_delete=(),
    ):
        new_events = []
        if isinstance(aggregates, BaseAggregateRoot):
            aggregates = [aggregates]
        for aggregate in aggregates:
            new_events += aggregate.__batch_pending_events__()
        process_event = ProcessEvent(
            domain_events=new_events,
            orm_objs_pending_save=orm_objects_pending_save,
            orm_objs_pending_delete=orm_objects_pending_delete,
        )
        new_records = self.record_process_event(process_event)
        # Find the head notification ID.
        notifiable_events = [e for e in new_events if e.__notifiable__]
        head_notification_id = None
        if len(notifiable_events):
            record_manager = self.event_store.record_manager
            notification_id_name = record_manager.notification_id_name
            notifications = []
            for record in new_records:
                if not hasattr(record, notification_id_name):
                    continue
                if not isinstance(getattr(record, notification_id_name), int):
                    continue
                notifications.append(
                    record_manager.create_notification_from_record(record))

            if len(notifications):
                head_notification_id = notifications[-1]["id"]
        self.publish_prompt(head_notification_id)
        for aggregate in aggregates:
            if self.repository.use_cache:
                self.repository.put_entity_in_cache(aggregate.id, aggregate)

    def record_process_event(self, process_event: ProcessEvent) -> List:
        # Construct event records.
        event_records = self.construct_event_records(
            process_event.domain_events, process_event.causal_dependencies)

        # Write event records with tracking record.
        record_manager = self.event_store.record_manager
        assert isinstance(record_manager, RecordManagerWithTracking)
        record_manager.write_records(
            records=event_records,
            tracking_kwargs=process_event.tracking_kwargs,
            orm_objs_pending_save=process_event.orm_objs_pending_save,
            orm_objs_pending_delete=process_event.orm_objs_pending_delete,
        )
        return event_records

    def construct_event_records(
        self,
        pending_events: Iterable[TAggregateEvent],
        causal_dependencies: Optional[ListOfCausalDependencies],
    ) -> List:
        # Convert to event records.
        sequenced_items = self.event_store.items_from_events(pending_events)
        record_manager = self.event_store.record_manager
        assert record_manager
        assert isinstance(record_manager, RecordManagerWithTracking)
        event_records = list(record_manager.to_records(sequenced_items))

        # Set notification log IDs, and causal dependencies.
        if len(event_records):
            # Todo: Maybe keep track of what this probably is, to
            #  avoid query. Like log reader, invalidate on error.
            if self.set_notification_ids:
                notification_id_name = record_manager.notification_id_name
                current_max = record_manager.get_max_notification_id()
                for domain_event, event_record in zip(pending_events,
                                                      event_records):
                    if type(domain_event).__notifiable__:
                        current_max += 1
                        setattr(event_record, notification_id_name,
                                current_max)
                    else:
                        setattr(event_record, notification_id_name,
                                "event-not-notifiable")

            if self.use_causal_dependencies:
                assert hasattr(record_manager.record_class,
                               "causal_dependencies")
                causal_dependencies_json = self.event_store.event_mapper.json_dumps(
                    causal_dependencies).decode("utf8")
                # Only need first event to carry the dependencies.
                event_records[0].causal_dependencies = causal_dependencies_json

        return event_records

    def publish_prompt(self, head_notification_id=None):
        prompt = PromptToPull(self.name, self.pipeline_id,
                              head_notification_id)
        try:
            publish(prompt)
        except PromptFailed:
            raise
        except Exception as e:
            raise PromptFailed("{}: {}".format(type(e), str(e)))
Ejemplo n.º 20
0
class SimpleApplication(object):
    persist_event_type = None

    def __init__(self,
                 name='',
                 persistence_policy=None,
                 persist_event_type=None,
                 uri=None,
                 pool_size=5,
                 session=None,
                 cipher_key=None,
                 sequenced_item_class=None,
                 stored_event_record_class=None,
                 setup_table=True,
                 contiguous_record_ids=True,
                 pipeline_id=-1,
                 notification_log_section_size=None):

        self.notification_log_section_size = notification_log_section_size
        self.name = name or type(self).__name__.lower()

        # Setup cipher (optional).
        self.setup_cipher(cipher_key)

        # Setup connection to database.
        self.setup_datastore(session, uri, pool_size)

        # Setup the event store.
        self.sequenced_item_class = sequenced_item_class
        self.stored_event_record_class = stored_event_record_class
        self.contiguous_record_ids = contiguous_record_ids
        self.application_id = uuid_from_application_name(self.name)
        self.pipeline_id = pipeline_id
        self.setup_event_store()

        # Setup notifications.
        self.notification_log = RecordManagerNotificationLog(
            self.event_store.record_manager,
            section_size=self.notification_log_section_size)

        # Setup an event sourced repository.
        self.setup_repository()

        # Setup a persistence policy.
        self.persistence_policy = persistence_policy
        if self.persistence_policy is None:
            self.setup_persistence_policy(persist_event_type
                                          or type(self).persist_event_type)

        # Setup table in database.
        if setup_table and not session:
            self.setup_table()

    def change_pipeline(self, pipeline_id):
        self.pipeline_id = pipeline_id
        self.event_store.record_manager.pipeline_id = pipeline_id

    @property
    def session(self):
        return self.datastore.session

    def setup_cipher(self, cipher_key):
        cipher_key = decode_random_bytes(cipher_key
                                         or os.getenv('CIPHER_KEY', ''))
        self.cipher = AESCipher(cipher_key) if cipher_key else None

    def setup_datastore(self, session, uri, pool_size=5):
        self.datastore = SQLAlchemyDatastore(
            settings=SQLAlchemySettings(uri=uri, pool_size=pool_size),
            session=session,
        )

    def setup_event_store(self):
        # Construct event store.
        self.event_store = self.construct_event_store(self.application_id,
                                                      self.pipeline_id)

    def construct_event_store(self, application_id, pipeline_id):
        return construct_sqlalchemy_eventstore(
            sequenced_item_class=self.sequenced_item_class,
            session=self.datastore.session,
            cipher=self.cipher,
            record_class=self.stored_event_record_class,
            contiguous_record_ids=self.contiguous_record_ids,
            application_id=application_id,
            pipeline_id=pipeline_id,
        )

    def setup_repository(self, **kwargs):
        event_store = self.event_store
        self.repository = self.construct_repository(event_store, **kwargs)

    def construct_repository(self, event_store, **kwargs):
        return EventSourcedRepository(event_store=event_store, **kwargs)

    def setup_persistence_policy(self, persist_event_type):
        self.persistence_policy = PersistencePolicy(
            event_store=self.event_store, event_type=persist_event_type)

    def setup_table(self):
        # Setup the database table using event store's record class.
        self.datastore.setup_table(
            self.event_store.record_manager.record_class)

    def drop_table(self):
        # Setup the database table using event store's record class.
        self.datastore.drop_table(self.event_store.record_manager.record_class)

    def close(self):
        # Close the persistence policy.
        if self.persistence_policy:
            self.persistence_policy.close()

        # Close database connection.
        self.datastore.close_connection()

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.close()
Ejemplo n.º 21
0
class SimpleApplication(Pipeable, Generic[TVersionedEntity, TVersionedEvent]):
    """
    Base class for event sourced applications.

    Constructs infrastructure objects such as the repository and
    event store, and also the notification log which presents the
    application state as a sequence of events.

    Needs actual infrastructure classes.
    """

    infrastructure_factory_class: Type[
        InfrastructureFactory] = InfrastructureFactory
    is_constructed_with_session: bool = False

    record_manager_class: Optional[Type[AbstractRecordManager]] = None
    stored_event_record_class: Optional[type] = None
    snapshot_record_class: Optional[type] = None

    sequenced_item_class: Optional[Type[NamedTuple]] = None
    sequenced_item_mapper_class: Optional[Type[SequencedItemMapper]] = None
    compressor: Any = None
    json_encoder_class: Optional[Type[JSONEncoder]] = None
    json_decoder_class: Optional[Type[JSONDecoder]] = None

    persist_event_type: Optional[PersistEventType] = None
    notification_log_section_size: Optional[int] = None
    use_cache: bool = False

    event_store_class: Type[EventStore] = EventStore
    repository_class: Type[EventSourcedRepository] = EventSourcedRepository

    def __init__(
        self,
        name: str = "",
        persistence_policy: Optional[PersistencePolicy] = None,
        persist_event_type: PersistEventType = None,
        cipher_key: Optional[str] = None,
        compressor: Any = None,
        sequenced_item_class: Optional[Type[NamedTuple]] = None,
        sequenced_item_mapper_class: Optional[
            Type[SequencedItemMapper]] = None,
        record_manager_class: Optional[Type[AbstractRecordManager]] = None,
        stored_event_record_class: Optional[type] = None,
        event_store_class: Optional[Type[EventStore]] = None,
        snapshot_record_class: Optional[type] = None,
        setup_table: bool = True,
        contiguous_record_ids: bool = True,
        pipeline_id: int = DEFAULT_PIPELINE_ID,
        json_encoder_class: Optional[Type[JSONEncoder]] = None,
        json_decoder_class: Optional[Type[JSONDecoder]] = None,
        notification_log_section_size: Optional[int] = None,
        use_cache: bool = False,
    ):
        self.name = name or type(self).__name__.lower()

        self.notification_log_section_size = notification_log_section_size

        sequenced_item_class = sequenced_item_class or type(
            self).sequenced_item_class
        sequenced_item_class = sequenced_item_class or StoredEvent  # type: ignore
        self.sequenced_item_class = sequenced_item_class
        assert self.sequenced_item_class is not None
        self.sequenced_item_mapper_class: Type[SequencedItemMapper] = (
            sequenced_item_mapper_class
            or type(self).sequenced_item_mapper_class or SequencedItemMapper)

        self.record_manager_class = (record_manager_class
                                     or type(self).record_manager_class)
        self._stored_event_record_class = stored_event_record_class
        self._snapshot_record_class = snapshot_record_class

        self.event_store_class = event_store_class or type(
            self).event_store_class

        self.json_encoder_class = json_encoder_class or type(
            self).json_encoder_class
        self.json_decoder_class = json_decoder_class or type(
            self).json_decoder_class
        self.persist_event_type = persist_event_type or type(
            self).persist_event_type

        self.contiguous_record_ids = contiguous_record_ids
        self.pipeline_id = pipeline_id
        self._persistence_policy = persistence_policy

        self.cipher = self.construct_cipher(cipher_key)
        self.compressor = compressor or type(self).compressor

        # Default to using zlib compression when encrypting.
        if self.cipher and self.compressor is None:
            self.compressor = zlib

        self.infrastructure_factory: Optional[
            InfrastructureFactory[TVersionedEvent]] = None
        self._datastore: Optional[AbstractDatastore] = None
        self._event_store: Optional[AbstractEventStore[
            TVersionedEvent, BaseRecordManager]] = None
        self._repository: Optional[EventSourcedRepository[
            TVersionedEntity, TVersionedEvent]] = None
        self._notification_log: Optional[LocalNotificationLog] = None

        self.use_cache = use_cache or type(self).use_cache

        if (self.record_manager_class
                or self.infrastructure_factory_class.record_manager_class):

            self.construct_infrastructure()

            if setup_table:
                self.setup_table()
            self.construct_notification_log()

            if self._persistence_policy is None:
                self.construct_persistence_policy()

    @property
    def datastore(self) -> AbstractDatastore:
        assert self._datastore
        return self._datastore

    @property
    def event_store(
            self) -> AbstractEventStore[TVersionedEvent, BaseRecordManager]:
        assert self._event_store
        return self._event_store

    @property
    def repository(
            self) -> EventSourcedRepository[TVersionedEntity, TVersionedEvent]:
        assert self._repository
        return self._repository

    @property
    def notification_log(self) -> LocalNotificationLog:
        assert self._notification_log
        return self._notification_log

    @property
    def persistence_policy(self) -> PersistencePolicy:
        assert self._persistence_policy
        return self._persistence_policy

    def construct_cipher(self,
                         cipher_key_str: Optional[str]) -> Optional[AESCipher]:
        cipher_key_bytes = decode_bytes(cipher_key_str
                                        or os.getenv("CIPHER_KEY", "") or "")
        return AESCipher(cipher_key_bytes) if cipher_key_bytes else None

    def construct_infrastructure(self, *args: Any, **kwargs: Any) -> None:
        self.infrastructure_factory = self.construct_infrastructure_factory(
            *args, **kwargs)
        self.construct_datastore()
        self.construct_event_store()
        self.construct_repository()

    def construct_infrastructure_factory(
            self, *args: Any, **kwargs: Any) -> InfrastructureFactory:
        """
        :rtype: InfrastructureFactory
        """
        factory_class = self.infrastructure_factory_class
        assert issubclass(factory_class, InfrastructureFactory)

        integer_sequenced_record_class = (self._stored_event_record_class
                                          or self.stored_event_record_class)
        snapshot_record_class = (self._snapshot_record_class
                                 or self.snapshot_record_class)

        return factory_class(  # type:ignore  # multiple values for keyword argument
            record_manager_class=self.record_manager_class,
            integer_sequenced_record_class=integer_sequenced_record_class,
            snapshot_record_class=snapshot_record_class,
            sequenced_item_class=self.sequenced_item_class,
            sequenced_item_mapper_class=self.sequenced_item_mapper_class,
            json_encoder_class=self.json_encoder_class,
            json_decoder_class=self.json_decoder_class,
            contiguous_record_ids=self.contiguous_record_ids,
            application_name=self.name,
            pipeline_id=self.pipeline_id,
            event_store_class=self.event_store_class,
            *args,
            **kwargs)

    def construct_datastore(self) -> None:
        assert self.infrastructure_factory
        self._datastore = self.infrastructure_factory.construct_datastore()

    def construct_event_store(self) -> None:
        assert self.infrastructure_factory
        factory = self.infrastructure_factory
        self._event_store = factory.construct_integer_sequenced_event_store(
            self.cipher, self.compressor)

    def construct_repository(self, **kwargs: Any) -> None:
        assert self.repository_class
        self._repository = self.repository_class(event_store=self.event_store,
                                                 use_cache=self.use_cache,
                                                 **kwargs)

    def setup_table(self) -> None:
        # Setup the database table using event store's record class.
        if self._datastore is not None:
            record_class = self.event_store.record_manager.record_class
            self.datastore.setup_table(record_class)

    def drop_table(self) -> None:
        # Drop the database table using event store's record class.
        if self._datastore is not None:
            record_class = self.event_store.record_manager.record_class
            self.datastore.drop_table(record_class)

    def construct_notification_log(self) -> None:
        self._notification_log = RecordManagerNotificationLog(
            self.event_store.record_manager,
            section_size=self.notification_log_section_size,
        )

    def construct_persistence_policy(self) -> None:
        self._persistence_policy = PersistencePolicy(
            event_store=self.event_store,
            persist_event_type=self.persist_event_type)

    def change_pipeline(self, pipeline_id: int) -> None:
        self.pipeline_id = pipeline_id
        self.event_store.record_manager.pipeline_id = pipeline_id

    def close(self) -> None:
        # Close the persistence policy.
        if self._persistence_policy is not None:
            self._persistence_policy.close()

        # Close database connection.
        if self._datastore is not None:
            self._datastore.close_connection()

    def __enter__(self: T) -> T:
        return self

    def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
        self.close()

    @classmethod
    def reset_connection_after_forking(cls) -> None:
        pass

    @classmethod
    def mixin(cls, infrastructure_class: type) -> type:
        return type(cls.__name__, (infrastructure_class, cls), {})
Ejemplo n.º 22
0
class SimpleApplication(object):
    persist_event_type = None
    sequenced_item_class = None
    sequenced_item_mapper_class = None
    infrastructure_factory_class = None
    record_manager_class = None
    stored_event_record_class = None
    snapshot_record_class = None
    json_encoder_class = None
    json_decoder_class = None
    is_constructed_with_session = False

    def __init__(self,
                 name='',
                 persistence_policy=None,
                 persist_event_type=None,
                 cipher_key=None,
                 sequenced_item_class=None,
                 sequenced_item_mapper_class=None,
                 infrastructure_factory_class=None,
                 record_manager_class=None,
                 stored_event_record_class=None,
                 snapshot_record_class=None,
                 setup_table=True,
                 contiguous_record_ids=True,
                 pipeline_id=-1,
                 json_encoder_class=None,
                 json_decoder_class=None,
                 notification_log_section_size=None):

        self.name = name or type(self).__name__.lower()

        self.notification_log_section_size = notification_log_section_size

        self.sequenced_item_class = sequenced_item_class \
                                    or type(self).sequenced_item_class \
                                    or StoredEvent

        self.sequenced_item_mapper_class = sequenced_item_mapper_class \
                                           or type(self).sequenced_item_mapper_class \
                                           or SequencedItemMapper

        self.infrastructure_factory_class = infrastructure_factory_class \
                                            or type(self).infrastructure_factory_class \
                                            or InfrastructureFactory

        self.record_manager_class = record_manager_class or type(
            self).record_manager_class

        self.stored_event_record_class = stored_event_record_class or type(
            self).stored_event_record_class

        self.snapshot_record_class = snapshot_record_class or type(
            self).snapshot_record_class

        self.json_encoder_class = json_encoder_class or type(
            self).json_encoder_class

        self.json_decoder_class = json_decoder_class or type(
            self).json_decoder_class

        self.persist_event_type = persist_event_type or type(
            self).persist_event_type

        self.contiguous_record_ids = contiguous_record_ids
        self.pipeline_id = pipeline_id
        self.setup_cipher(cipher_key)
        self.setup_infrastructure()
        if setup_table:
            self.setup_table()
        self.setup_notification_log()

        self.persistence_policy = persistence_policy
        if self.persistence_policy is None:
            self.setup_persistence_policy()

    def setup_cipher(self, cipher_key):
        cipher_key = decode_random_bytes(cipher_key
                                         or os.getenv('CIPHER_KEY', ''))
        self.cipher = AESCipher(cipher_key) if cipher_key else None

    def setup_infrastructure(self, *args, **kwargs):
        self.infrastructure_factory = self.construct_infrastructure_factory(
            *args, **kwargs)
        self.datastore = self.infrastructure_factory.construct_datastore()
        self.setup_event_store()
        self.setup_repository()

    def construct_infrastructure_factory(self, *args, **kwargs):
        """
        :rtype: InfrastructureFactory
        """
        return self.infrastructure_factory_class(
            record_manager_class=self.record_manager_class,
            integer_sequenced_record_class=self.stored_event_record_class,
            sequenced_item_class=self.sequenced_item_class,
            contiguous_record_ids=self.contiguous_record_ids,
            application_name=self.name,
            pipeline_id=self.pipeline_id,
            snapshot_record_class=self.snapshot_record_class,
            *args,
            **kwargs)

    def setup_event_store(self):
        # Construct event store.
        sequenced_item_mapper = self.sequenced_item_mapper_class(
            sequenced_item_class=self.sequenced_item_class,
            cipher=self.cipher,
            # sequence_id_attr_name=sequence_id_attr_name,
            # position_attr_name=position_attr_name,
            json_encoder_class=self.json_encoder_class,
            json_decoder_class=self.json_decoder_class,
        )
        record_manager = self.infrastructure_factory.construct_integer_sequenced_record_manager(
        )
        self.event_store = EventStore(
            record_manager=record_manager,
            sequenced_item_mapper=sequenced_item_mapper,
        )

    def setup_repository(self, **kwargs):
        self.repository = EventSourcedRepository(event_store=self.event_store,
                                                 **kwargs)

    def setup_table(self):
        # Setup the database table using event store's record class.
        if self.datastore is not None:
            self.datastore.setup_table(
                self.event_store.record_manager.record_class)

    def setup_notification_log(self):
        self.notification_log = RecordManagerNotificationLog(
            self.event_store.record_manager,
            section_size=self.notification_log_section_size)

    def setup_persistence_policy(self):
        self.persistence_policy = PersistencePolicy(
            event_store=self.event_store, event_type=self.persist_event_type)

    def change_pipeline(self, pipeline_id):
        self.pipeline_id = pipeline_id
        self.event_store.record_manager.pipeline_id = pipeline_id

    def drop_table(self):
        # Drop the database table using event store's record class.
        if self.datastore is not None:
            self.datastore.drop_table(
                self.event_store.record_manager.record_class)

    def close(self):
        # Close the persistence policy.
        if self.persistence_policy is not None:
            self.persistence_policy.close()

        # Close database connection.
        if self.datastore is not None:
            self.datastore.close_connection()

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.close()
Ejemplo n.º 23
0
class SimpleApplication(Pipeable, Generic[TVersionedEntity, TVersionedEvent]):
    """
    Base class for event sourced applications.

    Constructs infrastructure objects such as the repository and
    event store, and also the notification log which presents the
    application state as a sequence of events.

    Needs actual infrastructure classes.
    """

    infrastructure_factory_class: Type[
        InfrastructureFactory] = InfrastructureFactory
    is_constructed_with_session: bool = False

    record_manager_class: Optional[Type[AbstractRecordManager]] = None
    stored_event_record_class: Optional[type] = None
    snapshot_record_class: Optional[type] = None

    sequenced_item_class: Optional[Type[NamedTuple]] = None
    sequenced_item_mapper_class: Optional[Type[SequencedItemMapper]] = None
    compressor: Any = None
    json_encoder_class: Optional[Type[JSONEncoder]] = None
    sort_keys: bool = False
    json_decoder_class: Optional[Type[JSONDecoder]] = None

    persist_event_type: Optional[PersistEventType] = None
    notification_log_section_size: Optional[int] = None
    use_cache: bool = False

    event_store_class: Type[EventStore] = EventStore
    repository_class: Type[EventSourcedRepository] = EventSourcedRepository

    def __init__(
        self,
        name: str = "",
        persistence_policy: Optional[PersistencePolicy] = None,
        persist_event_type: PersistEventType = None,
        cipher_key: Optional[str] = None,
        compressor: Any = None,
        sequenced_item_class: Optional[Type[NamedTuple]] = None,
        sequenced_item_mapper_class: Optional[
            Type[SequencedItemMapper]] = None,
        record_manager_class: Optional[Type[AbstractRecordManager]] = None,
        stored_event_record_class: Optional[type] = None,
        event_store_class: Optional[Type[EventStore]] = None,
        snapshot_record_class: Optional[type] = None,
        setup_table: bool = True,
        contiguous_record_ids: bool = True,
        pipeline_id: int = DEFAULT_PIPELINE_ID,
        json_encoder_class: Optional[Type[JSONEncoder]] = None,
        sort_keys: bool = False,
        json_decoder_class: Optional[Type[JSONDecoder]] = None,
        notification_log_section_size: Optional[int] = None,
        use_cache: bool = False,
    ):
        """
        Initialises application object.

        :param name: Name of application.
        :param persistence_policy: Persistence policy object.
        :param persist_event_type: Tuple of domain event classes to be persisted.
        :param cipher_key: Base64 unicode string cipher key.
        :param compressor: Compressor used to compress serialized event state.
        :param sequenced_item_class: Named tuple for mapping and recording events.
        :param sequenced_item_mapper_class: Object class for mapping stored events.
        :param record_manager_class: Object class for recording stored events.
        :param stored_event_record_class: Object class for event records.
        :param event_store_class: Object class uses to store and retrieve domain events.
        :param snapshot_record_class: Object class used to represent snapshots.
        :param setup_table: Option to create database tables when application starts.
        :param contiguous_record_ids: Whether or not to delegate notification ID
            generation to the record manager (to guarantee there will be no gaps).
        :param pipeline_id: ID of instance of system pipeline expressions.
        :param json_encoder_class: Object class used to encode object as JSON strings.
        :param json_decoder_class: Object class used to decode JSON strings as objects.
        :param notification_log_section_size: Number of notification items in a section.
        :param use_cache: Whether or not to keep aggregates in memory (saves replaying
            when accessing again, but uses memory).
        """
        self.name = name or type(self).__name__.lower()

        self.notification_log_section_size = notification_log_section_size

        sequenced_item_class = sequenced_item_class or type(
            self).sequenced_item_class
        sequenced_item_class = sequenced_item_class or StoredEvent  # type: ignore
        self.sequenced_item_class = sequenced_item_class
        assert self.sequenced_item_class is not None
        self.sequenced_item_mapper_class: Type[SequencedItemMapper] = (
            sequenced_item_mapper_class
            or type(self).sequenced_item_mapper_class or SequencedItemMapper)

        self.record_manager_class = (record_manager_class
                                     or type(self).record_manager_class)
        self._stored_event_record_class = stored_event_record_class
        self._snapshot_record_class = snapshot_record_class

        self.event_store_class = event_store_class or type(
            self).event_store_class

        self.json_encoder_class = json_encoder_class or type(
            self).json_encoder_class
        self.sort_keys = sort_keys or type(self).sort_keys
        self.json_decoder_class = json_decoder_class or type(
            self).json_decoder_class
        self.persist_event_type = persist_event_type or type(
            self).persist_event_type

        self.contiguous_record_ids = contiguous_record_ids
        self.pipeline_id = pipeline_id
        self._persistence_policy = persistence_policy

        self.cipher = self.construct_cipher(cipher_key)
        self.compressor = compressor or type(self).compressor

        # Default to using zlib compression when encrypting.
        if self.cipher and self.compressor is None:
            self.compressor = zlib

        self.infrastructure_factory: Optional[
            InfrastructureFactory[TVersionedEvent]] = None
        self._datastore: Optional[AbstractDatastore] = None
        self._event_store: Optional[AbstractEventStore[
            TVersionedEvent, BaseRecordManager]] = None
        self._repository: Optional[EventSourcedRepository[
            TVersionedEntity, TVersionedEvent]] = None
        self._notification_log: Optional[LocalNotificationLog] = None

        self.use_cache = use_cache or type(self).use_cache

        if (self.record_manager_class
                or self.infrastructure_factory_class.record_manager_class):

            self.construct_infrastructure()

            if setup_table:
                self.setup_table()
            self.construct_notification_log()

            if self._persistence_policy is None:
                self.construct_persistence_policy()

    @property
    def datastore(self) -> AbstractDatastore:
        assert self._datastore
        return self._datastore

    @property
    def event_store(
            self) -> AbstractEventStore[TVersionedEvent, BaseRecordManager]:
        assert self._event_store
        return self._event_store

    @property
    def repository(
            self) -> EventSourcedRepository[TVersionedEntity, TVersionedEvent]:
        assert self._repository
        return self._repository

    @property
    def notification_log(self) -> LocalNotificationLog:
        assert self._notification_log
        return self._notification_log

    @property
    def persistence_policy(self) -> PersistencePolicy:
        assert self._persistence_policy
        return self._persistence_policy

    def construct_cipher(self,
                         cipher_key_str: Optional[str]) -> Optional[AESCipher]:
        cipher_key_bytes = decode_bytes(cipher_key_str
                                        or os.getenv("CIPHER_KEY", "") or "")
        return AESCipher(cipher_key_bytes) if cipher_key_bytes else None

    def construct_infrastructure(self, *args: Any, **kwargs: Any) -> None:
        """
        Constructs infrastructure for application.
        """
        self.infrastructure_factory = self.construct_infrastructure_factory(
            *args, **kwargs)
        self.construct_datastore()
        self.construct_event_store()
        self.construct_repository()

    def construct_infrastructure_factory(
            self, *args: Any, **kwargs: Any) -> InfrastructureFactory:
        """
        Constructs infrastructure factory object.
        """
        factory_class = self.infrastructure_factory_class
        assert issubclass(factory_class, InfrastructureFactory)

        integer_sequenced_record_class = (self._stored_event_record_class
                                          or self.stored_event_record_class)
        snapshot_record_class = (self._snapshot_record_class
                                 or self.snapshot_record_class)

        return factory_class(  # type:ignore  # multiple values for keyword argument
            record_manager_class=self.record_manager_class,
            integer_sequenced_record_class=integer_sequenced_record_class,
            snapshot_record_class=snapshot_record_class,
            sequenced_item_class=self.sequenced_item_class,
            sequenced_item_mapper_class=self.sequenced_item_mapper_class,
            json_encoder_class=self.json_encoder_class,
            sort_keys=self.sort_keys,
            json_decoder_class=self.json_decoder_class,
            contiguous_record_ids=self.contiguous_record_ids,
            application_name=self.name,
            pipeline_id=self.pipeline_id,
            event_store_class=self.event_store_class,
            *args,
            **kwargs)

    def construct_datastore(self) -> None:
        """
        Constructs datastore object (which helps by creating and dropping tables).
        """
        assert self.infrastructure_factory
        self._datastore = self.infrastructure_factory.construct_datastore()

    def construct_event_store(self) -> None:
        """
        Constructs event store object.
        """
        assert self.infrastructure_factory
        factory = self.infrastructure_factory
        self._event_store = factory.construct_integer_sequenced_event_store(
            self.cipher, self.compressor)

    def construct_repository(self, **kwargs: Any) -> None:
        """
        Constructs repository object.
        """
        assert self.repository_class
        self._repository = self.repository_class(event_store=self.event_store,
                                                 use_cache=self.use_cache,
                                                 **kwargs)

    def setup_table(self) -> None:
        """
        Sets up the database table using event store's record class.
        """
        if self._datastore is not None:
            record_class = self.event_store.record_manager.record_class
            self.datastore.setup_table(record_class)

    def drop_table(self) -> None:
        """
        Drops the database table using event store's record class.
        """
        if self._datastore is not None:
            record_class = self.event_store.record_manager.record_class
            self.datastore.drop_table(record_class)

    def construct_notification_log(self) -> None:
        """
        Constructs notification log object.
        """
        self._notification_log = RecordManagerNotificationLog(
            self.event_store.record_manager,
            section_size=self.notification_log_section_size,
        )

    def construct_persistence_policy(self) -> None:
        """
        Constructs persistence policy object.
        """
        self._persistence_policy = PersistencePolicy(
            event_store=self.event_store,
            persist_event_type=self.persist_event_type)

    def change_pipeline(self, pipeline_id: int) -> None:
        """
        Switches pipeline being used by this application object.
        """
        self.pipeline_id = pipeline_id
        self.event_store.record_manager.pipeline_id = pipeline_id

    def close(self) -> None:
        # Close the persistence policy.
        if self._persistence_policy is not None:
            self._persistence_policy.close()

        # Close database connection.
        if self._datastore is not None:
            self._datastore.close_connection()

    def __enter__(self: T) -> T:
        """
        Supports use of application as context manager.
        """
        return self

    def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
        """
        Closes application when exiting context manager.
        """
        self.close()

    @classmethod
    def reset_connection_after_forking(cls) -> None:
        pass

    @classmethod
    def mixin(cls, infrastructure_class: type) -> type:
        """
        Returns subclass that inherits also from given infrastructure class.
        """
        return type(cls.__name__, (infrastructure_class, cls), {})
Ejemplo n.º 24
0
class SimpleApplication(object):
    def __init__(self, persist_event_type=None, uri=None, session=None, cipher_key=None,
                 stored_event_record_class=None, setup_table=True, contiguous_record_ids=True):

        # Setup cipher (optional).
        self.setup_cipher(cipher_key)

        # Setup connection to database.
        self.setup_datastore(session, uri)

        # Setup the event store.
        self.stored_event_record_class = stored_event_record_class
        self.contiguous_record_ids = contiguous_record_ids
        self.setup_event_store()

        # Setup notifications.
        self.notification_log = RecordManagerNotificationLog(
            self.event_store.record_manager,
            section_size=20,
        )

        # Setup an event sourced repository.
        self.setup_repository()

        # Setup a persistence policy.
        self.setup_persistence_policy(persist_event_type)

        # Setup table in database.
        if setup_table:
            self.setup_table()

    def setup_cipher(self, cipher_key):
        cipher_key = decode_random_bytes(cipher_key or os.getenv('CIPHER_KEY', ''))
        self.cipher = AESCipher(cipher_key) if cipher_key else None

    def setup_datastore(self, session, uri):
        self.datastore = SQLAlchemyDatastore(
            settings=SQLAlchemySettings(uri=uri),
            session=session,
        )

    def setup_event_store(self):
        # Construct event store.
        self.event_store = construct_sqlalchemy_eventstore(
            session=self.datastore.session,
            cipher=self.cipher,
            record_class=self.stored_event_record_class,
            contiguous_record_ids=self.contiguous_record_ids,
        )

    def setup_repository(self, **kwargs):
        self.repository = EventSourcedRepository(
            event_store=self.event_store,
            **kwargs
        )

    def setup_persistence_policy(self, persist_event_type):
        self.persistence_policy = PersistencePolicy(
            event_store=self.event_store,
            event_type=persist_event_type
        )

    def setup_table(self):
        # Setup the database table using event store's record class.
        self.datastore.setup_table(
            self.event_store.record_manager.record_class
        )

    def drop_table(self):
        # Setup the database table using event store's record class.
        self.datastore.drop_table(
            self.event_store.record_manager.record_class
        )

    def close(self):
        # Close the persistence policy.
        self.persistence_policy.close()

        # Close database connection.
        self.datastore.close_connection()

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.close()
Ejemplo n.º 25
0
class WithPersistencePolicies(WithActiveRecordStrategies):
    """
    Base class for test cases that need persistence policies.
    """

    def setUp(self):
        super(WithPersistencePolicies, self).setUp()
        # Setup the persistence subscriber.
        self.entity_event_store = EventStore(
            active_record_strategy=self.entity_active_record_strategy,
            sequenced_item_mapper=SequencedItemMapper(
                sequenced_item_class=SequencedItem,
                sequence_id_attr_name='originator_id',
                position_attr_name='originator_version'
            )
        )
        self.log_event_store = EventStore(
            active_record_strategy=self.log_active_record_strategy,
            sequenced_item_mapper=SequencedItemMapper(
                sequenced_item_class=SequencedItem,
                sequence_id_attr_name='originator_id',
                position_attr_name='timestamp'
            )
        )
        self.snapshot_store = EventStore(
            active_record_strategy=self.snapshot_active_record_strategy,
            sequenced_item_mapper=SequencedItemMapper(
                sequenced_item_class=SequencedItem,
                sequence_id_attr_name='originator_id',
                position_attr_name='originator_version'
            )
        )

        self.integer_sequenced_event_policy = None
        if self.entity_event_store is not None:
            self.integer_sequenced_event_policy = PersistencePolicy(
                event_store=self.entity_event_store,
                event_type=VersionedEntity.Event,
            )

        self.timestamp_sequenced_event_policy = None
        if self.log_event_store is not None:
            self.timestamp_sequenced_event_policy = PersistencePolicy(
                event_store=self.log_event_store,
                event_type=Logged,
            )

        self.snapshot_policy = None
        if self.snapshot_store is not None:
            self.snapshot_policy = PersistencePolicy(
                event_store=self.snapshot_store,
                event_type=Snapshot,
            )

    def tearDown(self):
        # Close the persistence subscriber.
        if self.snapshot_policy:
            self.snapshot_policy.close()
        if self.timestamp_sequenced_event_policy:
            self.timestamp_sequenced_event_policy.close()
        if self.entity_event_store:
            self.integer_sequenced_event_policy.close()
        super(WithPersistencePolicies, self).tearDown()
Ejemplo n.º 26
0
class SimpleApplication(ABC):
    """
    Base class for event sourced applications.

    Constructs infrastructure objects such as the repository and
    event store, and also the notification log which presents the
    application state as a sequence of events.

    Needs actual infrastructure classes.
    """
    infrastructure_factory_class = InfrastructureFactory
    is_constructed_with_session = False

    record_manager_class = None
    stored_event_record_class = None
    snapshot_record_class = None

    sequenced_item_class = None
    sequenced_item_mapper_class = None
    json_encoder_class = None
    json_decoder_class = None

    persist_event_type = None
    notification_log_section_size = None
    use_cache = False

    event_store_class = EventStore
    repository_class = EventSourcedRepository

    def __init__(self,
                 name='',
                 persistence_policy=None,
                 persist_event_type=None,
                 cipher_key=None,
                 sequenced_item_class=None,
                 sequenced_item_mapper_class=None,
                 record_manager_class=None,
                 stored_event_record_class=None,
                 snapshot_record_class=None,
                 setup_table=True,
                 contiguous_record_ids=True,
                 pipeline_id=DEFAULT_PIPELINE_ID,
                 json_encoder_class=None,
                 json_decoder_class=None,
                 notification_log_section_size=None):
        self._datastore = None
        self._event_store = None
        self._repository = None
        self.infrastructure_factory = None

        self.name = name or type(self).__name__.lower()

        self.notification_log_section_size = notification_log_section_size

        self.sequenced_item_class = sequenced_item_class \
                                    or type(self).sequenced_item_class \
                                    or StoredEvent

        self.sequenced_item_mapper_class = sequenced_item_mapper_class \
                                           or type(self).sequenced_item_mapper_class \
                                           or SequencedItemMapper

        self.record_manager_class = record_manager_class or type(
            self).record_manager_class

        self.stored_event_record_class = stored_event_record_class or type(
            self).stored_event_record_class

        self.snapshot_record_class = snapshot_record_class or type(
            self).snapshot_record_class

        self.json_encoder_class = json_encoder_class or type(
            self).json_encoder_class

        self.json_decoder_class = json_decoder_class or type(
            self).json_decoder_class

        self.persist_event_type = persist_event_type or type(
            self).persist_event_type

        self.contiguous_record_ids = contiguous_record_ids
        self.pipeline_id = pipeline_id
        self.construct_cipher(cipher_key)

        self.persistence_policy = persistence_policy

        if self.record_manager_class or self.infrastructure_factory_class.record_manager_class:
            self.construct_infrastructure()

            if setup_table:
                self.setup_table()
            self.construct_notification_log()

            if self.persistence_policy is None:
                self.construct_persistence_policy()

    @property
    def datastore(self):
        return self._datastore

    @property
    def event_store(self):
        return self._event_store

    @property
    def repository(self):
        return self._repository

    def construct_cipher(self, cipher_key):
        cipher_key = decode_bytes(cipher_key or os.getenv('CIPHER_KEY', ''))
        self.cipher = AESCipher(cipher_key) if cipher_key else None

    def construct_infrastructure(self, *args, **kwargs):
        self.infrastructure_factory = self.construct_infrastructure_factory(
            *args, **kwargs)
        self.construct_datastore()
        self.construct_event_store()
        self.construct_repository()

    def construct_infrastructure_factory(self, *args, **kwargs):
        """
        :rtype: InfrastructureFactory
        """
        factory_class = self.infrastructure_factory_class
        assert issubclass(factory_class, InfrastructureFactory)
        return factory_class(
            record_manager_class=self.record_manager_class,
            integer_sequenced_record_class=self.stored_event_record_class,
            sequenced_item_class=self.sequenced_item_class,
            contiguous_record_ids=self.contiguous_record_ids,
            application_name=self.name,
            pipeline_id=self.pipeline_id,
            snapshot_record_class=self.snapshot_record_class,
            *args,
            **kwargs)

    def construct_datastore(self):
        self._datastore = self.infrastructure_factory.construct_datastore()

    def construct_event_store(self):
        # Construct event store.
        sequenced_item_mapper = self.sequenced_item_mapper_class(
            sequenced_item_class=self.sequenced_item_class,
            cipher=self.cipher,
            # sequence_id_attr_name=sequence_id_attr_name,
            # position_attr_name=position_attr_name,
            json_encoder_class=self.json_encoder_class,
            json_decoder_class=self.json_decoder_class,
        )
        record_manager = self.infrastructure_factory.construct_integer_sequenced_record_manager(
        )
        self._event_store = self.event_store_class(
            record_manager=record_manager,
            sequenced_item_mapper=sequenced_item_mapper,
        )

    def construct_repository(self, **kwargs):
        self._repository = self.repository_class(event_store=self.event_store,
                                                 use_cache=self.use_cache,
                                                 **kwargs)

    def setup_table(self):
        # Setup the database table using event store's record class.
        if self.datastore is not None:
            self.datastore.setup_table(
                self.event_store.record_manager.record_class)

    def drop_table(self):
        # Drop the database table using event store's record class.
        if self.datastore is not None:
            self.datastore.drop_table(
                self.event_store.record_manager.record_class)

    def construct_notification_log(self):
        self.notification_log = RecordManagerNotificationLog(
            self.event_store.record_manager,
            section_size=self.notification_log_section_size)

    def construct_persistence_policy(self):
        self.persistence_policy = PersistencePolicy(
            event_store=self.event_store,
            persist_event_type=self.persist_event_type)

    def change_pipeline(self, pipeline_id):
        self.pipeline_id = pipeline_id
        self.event_store.record_manager.pipeline_id = pipeline_id

    def close(self):
        # Close the persistence policy.
        if self.persistence_policy is not None:
            self.persistence_policy.close()

        # Close database connection.
        if self.datastore is not None:
            self.datastore.close_connection()

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.close()

    @classmethod
    def reset_connection_after_forking(cls):
        pass

    @classmethod
    def mixin(cls, *bases):
        return type(cls.__name__, bases + (cls, ), {})

    @classmethod
    def bind(cls, *bases, **kwargs):

        process_class = cls.mixin(*bases)
        if not issubclass(process_class,
                          ApplicationWithConcreteInfrastructure):
            raise Exception("Does not have infrastructure: {}, {}".format(
                cls, tuple(bases)))
        return process_class(**kwargs)