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, TypeAll 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 TicTacToeConfigCQRSAPIApp (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 SessionDisconnectedAll 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 GetUserAnd 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?
DomainModules 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()