Skip to content

Services

Define your training logic or whatever you are going to do with services

Service

A SERVICE is the technical authority for a business capability. And it is the exclusive owner of a certain subset of the business data. It centralizes and organizes domain operations, enforces business rules, and coordinates workflows.

The Service serves as an entry point for the service layer and provides a simple way to build stateless logic for executing domain operations.

A SERVICE should be modeled with UBIQUITOUS LANGUAGE. This means that the names of handler functions should reflect the domain operations that the service is responsible for. Keep this in mind when naming the functions that will be registered as handlers, since the Service class provides a method to call the handlers by their registered name. This is useful for example when building REST APIs with Command Query Segregation (CQS) and you want to invoke a handler based on the action they perfom (aka. The handler's name).

A naming generator can be provided to the Service constructor in order to customize the function names to the ubiquitous language of the domain. The default generator transforms the function name from snake_case to kebab-case.

Methods:

Name Description
register

Registers a handler for a specific command or query type. Handles nested or generic annotations.

handler

Decorator for registering a function as a handler.

handle

Executes the handler associated with a given action.

Example
from torch import cuda
from torchsystem import Depends
from torchsystem.services import Service

service = Service()

def device() -> str:
    raise NotImplementedError('Override this function to return the device')

@service.handler
def train(model: Model, data: DataLoader, device: str = Depends(device)):
    # Your training logic here
    ...

service.dependency_overrides[device] = lambda: 'cuda' if cuda.is_available() else 'cpu'

dependency_overrides property

An entry point for overriding the dependencies for the service. This is useful for late binding, testing and changing the behavior of the service in runtime.

Returns:

Name Type Description
dict dict

A dictionary of the dependency map.

Example
service = Service()
...

service.dependency_overrides[device] = lambda: 'cuda' if cuda.is_available() else 'cpu'

handle(action, *arguments)

Executes the handler associated with the given action. The action is the generated name from the handler function to be executed. The name is generated by the generator function provided to the service, which defaults to transforming the function name from snake case to kebab case.

Parameters:

Name Type Description Default
action str

The action to execute the handler for.

required

Raises:

Type Description
KeyError

If the handler for the action is not found.

Returns:

Name Type Description
Any Any

Whatever the handler returns.

Example
service = Service()

@service.handler
def train_model(model: Model, data: DataLoader, device: str = Depends(device)):
    # Your training logic here
    ...

model = Model()
data = Data()

service.handle('train-model', model, data) # train(model, data) will also work
                                           # but this is usefull when building REST APIs
                                           # with Command Query Segregation (CQS)

handler(wrapped)

Decorator for registering a function as a handler in the service. The handler is registered with the name of the function as the key. The handler is also injected with the dependencies provided by the service.

Parameters:

Name Type Description Default
wrapped Callable[..., Any]

The function to be registered as a handler.

required

Returns:

Type Description
Callable[..., Any]

Callable[..., Any]: The injected handler function.