General services

Our service implementation is collected in valuea_framework.broker, which contains the necessary components to construct services and listen for messages on a RabbitMQ queue (using valuea_framework.connectors.rabbitmq.Consumer.RabbitMQRPCConsumer).

How to use services is described in detail in our examples (1. “Hello world!” Service).

BaseService

This is the default service type from where all other services should inherit, either direct or indirect (subclassing direct descendants). Basically this type of service implements all the default handles needed to fit the framework, such as extendable message validation, execution and message parsing.

ServiceRelational

When using sqlalchemy, this type of service prepares a connection and provides some guidance to return standard objects to the user interface (with object_to_ui).

Using the data from 8. Relational modeling we can output an object using the following example:

services/valuea/samples/get_user_object.json
 1{
 2	"$schema": "http://json-schema.org/draft-04/schema#",
 3	"description": "get_user_object",
 4	"properties": {
 5		"id": {
 6			"type": ["int", "null"]
 7		}
 8	},
 9	"required": [
10		"id"
11	]
12}
services/valuea/samples/get_user_object.py
 1import valuea_framework.broker.ServiceRelational
 2import model.valuea.sample
 3
 4
 5class Service(valuea_framework.broker.ServiceRelational):
 6    def __init__(self, *args, **kwargs):
 7        super(Service, self).__init__(*args, **kwargs)
 8
 9    def execute(self):
10        msg = self.get_message(True)
11        user = None
12        if msg.id:
13            user = self.rdbms_session.query(model.valuea.sample.User).get(msg.id)
14        if not user:
15            user = model.valuea.sample.User().load_object_defaults()
16
17        return self.object_to_ui(user)

When execute() is called, this method will try to fetch the requested record [line 13], when not found or an id wasn’t provided we return a default object. This should ensure that we always receive a structure we can interpret in the user interface.

Test it locally using:

run_get_user_object_local.py
1#!/usr/bin/env python
2
3import services.valuea.samples.get_user_object
4
5srv = services.valuea.samples.get_user_object.Service()
6srv.set_message({'id': 1})
7print (srv.execute())

Note

When the model object passed into object_to_ui contains foreign keys or choice fields, these will be converted to lists of items containing all the available and selected options.

ServiceRelationalSearch

This type of service derives from ServiceRelational and contains the default parameters and handles needed for the grid component we use (bootgrid).

services/valuea/samples/search_users.json
 1{
 2	"$schema": "http://json-schema.org/draft-04/schema#",
 3	"description": "search_users",
 4	"properties": {
 5		"rowCount": {
 6			"type": "integer"
 7		},
 8		"current": {
 9			"type": "integer"
10		},
11		"searchPhrase": {},
12		"sort": {}
13	},
14	"required": [
15		"rowCount"
16	]
17}
services/valuea/samples/search_users.py
 1import valuea_framework.broker.ServiceRelationalSearch
 2import model.valuea.sample
 3
 4
 5class Service(valuea_framework.broker.ServiceRelationalSearch):
 6    __query_fieldnames__ = ['id', 'name']
 7    __search_fieldnames__ = ['name']
 8
 9    def get_query(self):
10        return self.rdbms_session.query(model.valuea.sample.User).subquery()
11
12    def __init__(self, *args, **kwargs):
13        super(Service, self).__init__(*args, **kwargs)

The __query_fieldnames__ and __search_fieldnames__ define the fields returned by the call and get_query() specifies which data should be acquired using this search operation.

To test this locally, use:

run_search_users_local.py
1#!/usr/bin/env python
2
3import services.valuea.samples.search_users
4
5srv = services.valuea.samples.search_users.Service()
6srv.set_message({'rowCount': 10})
7print (srv.execute())

ServiceDistributed

Our distributed service can be used to compute in parallel, a configuration file is needed to setup the communication, similar to:

config/mq.conf
1[default]
2host=192.168.56.101
3username=admin
4password=admin
5exchange=default_exchange

Where the tag [default] is used to refer to a specific channel setup, which can use a different queue configuration.

The service itself supports two main handles, push_request(endpoint, content) to push messages back to the queue for further processing and fetch_results() to fetch results until all requests are processed or timeouts are reached.

Conceptually this type of service is used as a message coordinator which divides work until all is done.

Tip

When using a distributed service, make sure you either use different queues for the workers to avoid blocking regular services or throttle the number of request pushed at the same time with the service.

ServiceQueueOnly

The queue only service type is available for asynchronous services. Like the ServiceDistributed this type needs the same configuration to identify the target which will receive the asynchronous commands.

A regular workflow for an asynchronous service looks like this:

Where the execute is similar to any other type of service, which should use the queue action to offload the actual command to another worker and return the generated message id back to the user.

Our service might look similar to the following, where we are going to execute the valuea.samples.plusone service asynchronous, if extensive message logging is enabled you should be able to fetch the result although it is not send to any real client.

services/valuea/samples/requeue.py
 1import valuea_framework.broker
 2
 3
 4class Service(valuea_framework.broker.ServiceQueueOnly):
 5    def __init__(self, *args, **kwargs):
 6        super(Service, self).__init__(*args, **kwargs)
 7
 8    def execute(self):
 9        return {'messageid': self.queue('valuea.samples.plusone', {'x': 1})}
10