Event-Sourced System ==================== After you have defined your domain model, applications and projections, it is time to tie everything together. For that purpose, you use `EventSourcedSystem` class (referred to as the system), which orchestrates communication between applications and projections and executes commands and queries. Firstly, the system is a boundary between your domain logic and the outside world. The outside world never calls use cases in your applications directly or retrieve data from projections; instead, you prepare commands and queries, and execute them using the system's `execute_command` and `execute_query` methods. Such an approach allows you to hide the complexity of your business domain from the outside world behind the simple interface and gives you more flexibility to adapt your system for future changes. Second, the system is responsible for the initialization and orchestration of applications and projections. You initialize them inside the system, and route commands and queries to the responsible handlers. Defining the system ------------------- To create a system use `EventSourcedSystem` class: .. code-block:: python from bestagon.core.system import EventSourcedSystem class BondsSystem(EventSourcedSystem): def __init__(event_store: EventStore, checkpoint_store: CheckpointStore, neo_driver: AsyncDriver): super().__init__(event_store=event_store, checkpoint_store=checkpoint_store) self.repository = EventSourcedRepository(event_store=event_store) self.application = BondsApplication(repository=self.repository, checkpoint_store=checkpoint_store) self.bonds_projection = BondsProjection(checkpoint_store=checkpoint_store, driver=neo_driver) The system takes several parameters as input: - event_store - it should be an instance of `EventStore` class to store events, generated by your model. - checkpoint_store - an instance of `CheckpointStore` class to store information about the last processed event for applications and projections. - Additional parameters, necessary for your system. In this case, it is an instance of AsyncDriver to access the neo4j database. Next we create an instance of `EventSourcedRepository` class. It will be used by applications to save and retrieve aggregates. There is no use for multiple repositories, as in the traditional approach where you create a separate repository for each aggregate, event-sourcing allows us to greatly simplify this pattern and use a single repository for all aggregates. After the repository, you define your applications. Here we have only one application - `BondsApplication`, which takes the repository and checkpoint store as parameters. Similarly, you add projections. In our example, there is only one projection - `BondsProjection` which takes the checkpoint store and neo4j driver as input parameters. System initialization --------------------- It is not enough to define your applications and projections inside the system. To make it work, you need to initialize them and create subscriptions in the event store. For that purpose, you need to reimplement the `initialize` method and add your applications and projections to the system. To add applications, use the `add_application` method, to add projections, use the 'add_projection' method. Under the hood framework will make the necessary initializations and create subscriptions to the event store. .. code-block:: python async def initialize(self) -> None: await super().initialize() await self.add_application(self.bonds_application) await self.add_projection(self.bonds_projection) Routing commands and queries ---------------------------- The last thing to do is to register command and query handlers, so the system would know how exactly to execute commands and queries. For that purpose, you use an instance of `Mapper` class and use its `register_command_handler` and `register_query_handler` methods. One place to do that is inside `register_command_handlers` and `register_query_handlers`of the `EventSourcedSystem` class: .. code-block:: python from bestagon.core.mapper import mapper # Registering commands and quries inside the system def register_command_handlers(self) -> None: mapper.register_command_handler(IssueBond, self.bonds_application.issue) mapper.register_commant_handler(UpdateTermToMaturity, self.bonds_aplication.update_term_to_maturity) mapper.register_command_handler(SetMatured, self.bonds_applicaiton.set_matured) def register_query_handlers(self) -> None: mapperregister_query_handler(GetBondsQuery, self.bonds_projection.get_bonds) After that, your system is ready to be used. In the next tutorial you will learn how to serve your system across the network using FastAPI.