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. |