Key Concepts

Handler

Handler is the main concept of all Happyly library. Basically a handler is a callable which implements business logic, and nothing else:

  • No serialization/deserialiation here

  • No sending stuff over the network

  • No message queues’ related stuff

Let the handler do its job!

To create a handler you can simply define a function which takes a dict as an input and returns a dict:

def handle_my_stuff(message: dict):
    try
        db.update(message['user'], message['status'])
        return {
            'request_id': message['request_id'],
            'action': 'updated',
        }
    except Exception:
        return {
            'action': 'failed'
        }

Done! This handler can be plugged into your application: whether it uses Flask or Celery or whatever.

Note that you are allowed to return nothing if you don’t actually need a result from your handler. This handler is also valid:

def handle_another_stuff(message: dict):
    try
        neural_net.start_job(message['id'])
        _LOGGER.info('Job created')
    except Exception:
        _LOGGER.warning('Failed to create a job')

If you prefer class-based approach, Happyly can satisfy you too. Subclass happyly.Handler() and implement the following methods:

class MyHandler(happyly.Handler):

    def handle(message: dict)
        db.update(message['user'], message['status'])
        return {
            'request_id': message['request_id'],
            'action': 'updated',
        }

    def on_handling_failed(message: dict, error)
        return {
            'action': 'failed'
        }

Instance of MyHandler is equivalent to handle_my_stuff

Executor

To plug a handler into your application you will need happyly.Executor() (or one of its subclasses).

Executor brings the handler into a context of more pipeline steps:

  • deserialization

  • handling itself

  • serialization (optional)

  • publishing (optional)

So a typical construction of an Executor looks like this:

my_executor = Executor(
  deserializer=...
  handler=...
  serializer=...
  publisher=...
)

Executor implements two crucial methods: run() and run_for_result(). run(message) starts an execution pipeline for the provided message. run() returns nothing but can optionally publish a serialized result of handling.

_images/run.png

If you’d like to deal with the result by yourself, use run_for_result() which returns a serialized result of handling.

images/run_for_result.png

Executor manages all the stages of the pipeline, including situation when some stage fails. But the implementation of any stage itself (deserialization, handling, serialization, publishing) is provided to a constructor during executor instantiation.

You can use pre-made implementation of stages provided by Happyly or create you own (see Stages)

To customize what happens between the stages use Callbacks.

Probably you don’t want to invoke run() each time. You can bind an executor to some event by passing a subscriber to Executor()’s constructor.

There used to be a special component - Listener - for that, but it is deprecated now.