Package: didiator
didiatoris an asynchronous library that implements the Mediator pattern and
uses theDIlibrary to help you to inject dependencies to called handlersThis library is inspired by theMediatRused in C#,
follows CQRS principles and implements event publishingInstallationDidiator is available on pypi:https://pypi.org/project/didiatorpipinstall-U"didiator[di]"It will installdidiatorwith its optional DI dependency that is necessary to useDiMiddlewareandDiBuilderImplExamplesYou can find more examples inthis folderCreate Commands and Queries with handlers for them@dataclassclassCreateUser(Command[int]):user_id:intusername:strclassCreateUserHandler(CommandHandler[CreateUser,int]):def__init__(self,user_repo:UserRepo)->None:self._user_repo=user_repoasyncdef__call__(self,command:CreateUser)->int:user=User(id=command.user_id,username=command.username)awaitself._user_repo.add_user(user)awaitself._user_repo.commit()returnuser.idYou can use functions as handlers@dataclassclassGetUserById(Query[User]):user_id:intasyncdefhandle_get_user_by_id(query:GetUserById,user_repo:UserRepo)->User:user=awaituser_repo.get_user_by_id(query.user_id)returnuserCreate DiBuilderDiBuilderImplis a facade for Container from DI with caching ofsolvingdi_scopesis a list with the order ofscopesdi_builder.bind(...)willbindUserRepoImpltype toUserRepoprotocoldi_scopes=["request"]di_builder=DiBuilderImpl(Container(),AsyncExecutor(),di_scopes)di_builder.bind(bind_by_type(Dependent(UserRepoImpl,scope="request"),UserRepo))Create MediatorCreate dispatchers with their middlewares and use them to initialize theMediatorImplcls_scopeis a scope that will be used to bind class Command/Query handlers initialized during request handlingmiddlewares=(LoggingMiddleware(),DiMiddleware(di_builder,scopes=DiScopes("request")))command_dispatcher=CommandDispatcherImpl(middlewares=middlewares)query_dispatcher=QueryDispatcherImpl(middlewares=middlewares)mediator=MediatorImpl(command_dispatcher,query_dispatcher)Register handlers# CreateUserHandler is not initialized during registrationmediator.register_command_handler(CreateUser,CreateUserHandler)mediator.register_query_handler(GetUserById,handle_get_user_by_id)Main usageEnter the"request"scope that was registered earlier and create a new Mediator withdi_stateboundUsemediator.send(...)for commands andmediator.query(...)for queriesasyncwithdi_builder.enter_scope("request")asdi_state:scoped_mediator=mediator.bind(di_state=di_state)# It will call CreateUserHandler(UserRepoImpl()).__call__(command)# UserRepoImpl() created and injected automaticallyuser_id=awaitscoped_mediator.send(CreateUser(1,"Jon"))# It will call handle_get_user_by_id(query, user_repo)# UserRepoImpl created earlier will be reused in this scopeuser=awaitscoped_mediator.query(GetUserById(user_id))print("User:",user)# Session of UserRepoImpl will be closed after exiting the "request" scopeEvents publishingYou can register and publish events usingMediatorand itsEventObserver.
Unlike dispatchers,EventObserverpublishes events to multiple event handlers subscribed to it
and doesn’t return their result.
All middlewares also work withEventObserver, as in in the case with Dispatchers.Define event and its handlersclassUserCreated(Event):user_id:intusername:strasyncdefon_user_created1(event:UserCreated,logger:Logger)->None:logger.info("User created1: id=%s,  username=%s",event.user_id,event.username)asyncdefon_user_created2(event:UserCreated,logger:Logger)->None:logger.info("User created2: id=%s,  username=%s",event.user_id,event.username)Create EventObserver and use it for Mediatormiddlewares=(LoggingMiddleware(),DiMiddleware(di_builder,scopes=DiScopes("request")))event_observer=EventObserver(middlewares=middlewares)mediator=MediatorImpl(command_dispatcher,query_dispatcher,event_observer)Register event handlersYou can register multiple event handlers for one eventmediator.register_event_handler(UserCreated,on_user_created1)mediator.register_event_handler(UserCreated,on_user_created2)Publish eventEvent handlers will be executed sequentiallyawaitmediator.publish(UserCreated(1,"Jon"))# User created1: id=1,  username="Jon"# User created2: id=1,  username="Jon"awaitmediator.publish([UserCreated(2,"Sam"),UserCreated(3,"Nick")])# User created1: id=2,  username="Sam"# User created2: id=2,  username="Sam"# User created1: id=3,  username="Nick"# User created2: id=3,  username="Nick"⚠️Attention: this is a beta version ofdidiatorthat depends onDI,which is also in beta. Both of them can change their API!CQRSCQRS stands for “Command Query Responsibility Segregation”.
Its idea about splitting the responsibility of commands (writing) and queries (reading) into different models.didiatorhave segregated.send(command),.query(query)and.publish(events)methods in itsMediatorand
assumes that you will separate its handlers.
UseCommandMediator,QueryMediatorandEventMediatorprotocols to explicitly define which method you need inYourControllergraph LR;
    YourController-- Query -->Mediator;
    YourController-- Command -->Mediator;
    Mediator-. Query .->QueryDispatcher-.->di2[DiMiddleware]-.->QueryHandler;
    Mediator-. Command .->CommandDispatcher-.->di1[DiMiddleware]-.->CommandHandler;
    CommandHandler-- Event -->Mediator;
    Mediator-. Event .->EventObserver-.->di3[DiMiddleware]-.->EventHandler1;
    EventObserver-.->di4[DiMiddleware]-.->EventHandler2;DiMiddlewareinitializes handlers and injects dependencies for them, you can just send a command with the data you needWhydidiator?Easy dependency injection to your business logicSeparating dependencies from your controllers. They can just parse external requests and interact with theMediatorCQRSEvent publishingFlexible configurationMiddlewares supportWhy not?You don’t need itMaybe too low coupling: navigation becomes more difficultDidiator is in beta nowNo support for synchronous handlers