This is an example of using Scuti to implement a multiplayer distributed tic tac toe game. Scuti is a set of libraries and the minimal code to implement a bus that delivers commands, events and queries to handlers that change (commands and events) or retrieve (queries) the state of the system. It is a highly customizable piece of code that, once customized, offers a clear view of the simple architecture showing a clear path to the developer to implement features. The main pattern Scuti favors is CQS.
This example contains two parts:
import logging
from typing import List, Type
All code related to application infrastructure is placed in the applications
module. In this case an application
called api
is created.
from applications.api.application_infrastructure_module import ApplicationInfrastructureModule
from applications.api.config import TicTacToeConfig
CQRSAPIApp
(see cqrs_api_app.py) is an opinionated way to expose the domain to the network. This is not
included in Scuti as
wiring to
the outside world can be a complex thing that needs customization so it is outside Scuti scope.
It includes a:
/commands
, /queries
, /events
)handle
methods.from applications.api.cqrs_api_app import CQRSAPIApp
from applications.api.websockets.commands import AssociateUserToSession
from applications.api.websockets.events import SessionDisconnected
All domain logic is placed in the domain python module and split in subdomains according to the designed model.
from domain.games.scoring.domain_module import ScoringDomainModule
from domain.games.scoring.events import TopThreeListUpdated
from domain.games.scoring.queries import GetTopThreePlayers
from domain.games.tic_tac_toe.commands import CreateGame, JoinGame, PlaceMark
from domain.games.tic_tac_toe.game_domain_module import TicTacToeDomainModule
from domain.games.tic_tac_toe.events import BoardUpdated, GameEnded, GameErrorOccurred, GameStarted, MarkPlaced, \
TurnTimeout, WaitingForPlayerPlay
from domain.users.commands import CreateUser
from domain.users.users_domain_module import UserDomainModule
from domain.users.events import UserInvited
from domain.users.online.events import UserConnected, UsersOnlineUpdated
from domain.users.online.queries import GetUsersOnline
from domain.users.queries import GetUser
And finally some helper code to manage effects(commands, queries) and a DomainApplication
that models a set of
subdomains that:
- Can be started or stopped
- Can define some dependency injection bindings
- Can run some code in threads
- Have a set of effect handlers that take case of changing the system and publishing state
from scuti.domain.cqrs.effects import Command, Event, Query
from scuti.infrastructure.logging.get_logger import get_logger
logger = get_logger(__name__)
Your app starts here! Let’s go.
def main():
Boring logging stuff
logging.basicConfig(level=logging.DEBUG)
logger.setLevel(logging.DEBUG)
logger.info(f"API starting...")
Which sub domains should be loaded?
DomainModule
s define the shape of a subdomain. We’ll get into this later but, as can be seen, in this case
there is something about Tic tac toe rules, something about users, something about scoring and some application
stuff.
See: domain/games/tic_tac_toe/game_domain_module.py
Another interesting example is users domain Module: domain/users/users_domain_module.py
domains = [TicTacToeDomainModule, UserDomainModule, ScoringDomainModule, ApplicationInfrastructureModule]
These are the events that will be published to the network using Websockets
events_to_publish: List[Type[Event]] = [
GameStarted,
BoardUpdated,
WaitingForPlayerPlay,
GameErrorOccurred,
MarkPlaced,
GameEnded,
TopThreeListUpdated,
UsersOnlineUpdated,
UserInvited,
TurnTimeout
]
These are the events that can come from other contexts through network
accepted_events: List[Type[Event]] = [UserConnected, UserInvited, SessionDisconnected]
The commands that this domain accepts
accepted_commands: List[Type[Command]] = [AssociateUserToSession, CreateGame, PlaceMark, CreateUser, JoinGame]
The queries that this domain accepts
accepted_queries: List[Type[Query]] = [GetTopThreePlayers, GetUsersOnline, GetUser]
Some application config. This could come from env vars or a nice Toml file.
config = TicTacToeConfig(host="0.0.0.0", port=8080)
CQRSAPIApp
is your app that glues all the libraries, Scuti and runs the processes. This is meant to be created by
the
user here you can find an example but feel free to create your own.
api_app = CQRSAPIApp(config=config,
domains=domains,
accepted_commands=accepted_commands,
events_to_publish=events_to_publish,
accepted_events=accepted_events,
accepted_queries=accepted_queries)
logger.info(f"API listening on: {config.host}:{config.port}")
api_app.start()
if __name__ == "__main__":
main()