Invoking other services

Note

This chapter documents how other services running in a Zato cluster can be invoked. Visit here for information how to establish connections to external services and resources.

Choosing a mode of invocation

Services can be invoked synchronously or in an asynchronous manner so that you can choose whether the invoking service is blocked waiting for the response or if the other service should be run in background.

Note that for the latter, you won’t receive a response directly, instead, when invoking another service asynchronously you are given a CID the service being invoked will also receive so that its response, if any, can be correlated later on, using other Zato mechanisms, specific to your application.

Regardless of the invocation method, there is no difference in how requests are passed in.

If a service is being run programmatically its channel attribute will be ‘invoke’ or ‘invoke-async’ depending on the invocation style.

A service cannot invoke itself synchronously. It’s possible for a service to invoke itself asynchronously but doing so will create an infinite loop.

How to pass a request to another service

  • Passing a request is most convenient if another service uses Simple IO (SIO), in such cases it’s possible to use a plain Python dictionary as input data.

    Note

    Zato uses SIO in its own services and this is how they can be invoked.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    from zato.server.service import Service
    
    class MyService(Service):
        def handle(self):
    
            # A regular dictionary will do when invoking an SIO service
            input_data = {'cluster_id': 1}
    
            response = self.invoke('zato.security.get-list', input_data, as_bunch=True)
            for item in response.zato_security_get_list_response:
                self.logger.info(item.name)
    
  • Use the 'data_format' parameter if a service expects the incoming data to be in a particular data format, such as JSON or XML.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    # stdlib
    from json import dumps
    
    # Zato
    from zato.common import DATA_FORMAT
    from zato.server.service import Service
    
    class MyService(Service):
        def handle(self):
    
            input_data = {'customer_id': 123, 'phone_no': '123-456-789'}
            input_data = dumps(input_data) # Convert to JSON
    
            service_name = 'invoking.my-service2'
            self.invoke(service_name, input_data, data_format=DATA_FORMAT.JSON)
    
            self.logger.info(response)
    
    class MyService2(Service):
        def handle(self):
    
            # Note how the payload is already a Python dictionary
            # and doesn't need to be parsed manually
            self.logger.info(self.request.payload['customer_id'])
    
  • Same thing but done asynchronously:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    # stdlib
    from json import dumps
    
    # Zato
    from zato.common import DATA_FORMAT
    from zato.server.service import Service
    
    class MyService(Service):
        def handle(self):
    
            input_data = {'customer_id': 123, 'phone_no': '123-456-789'}
            input_data = dumps(input_data) # Convert to JSON
    
            service_name = 'invoking.my-service2'
            self.invoke_async(service_name, input_data, data_format=DATA_FORMAT.JSON)
    
            self.logger.info(response)
    
    class MyService2(Service):
        def handle(self):
    
            # Note how the payload is already a Python dictionary
            # and doesn't need to be parsed manually
            self.logger.info(self.request.payload['customer_id'])
    

Reading responses

  • SIO services will produce dictionaries as their responses and you can choose to use them directly or to convert a response to a Bunch in order to use the dot-notation when accessing the response's elements. Thus, the examples below are equivalent.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    from zato.server.service import Service
    
    class MyService(Service):
        def handle(self):
            response = self.invoke('invoking.my-service2')
            self.logger.info(response['my_response_elem']['cust_name'])
    
    class MyService2(Service):
        class SimpleIO:
            input_required = ('cust_id',)
            output_required = ('cust_name',)
            response_elem = 'my_response_elem'
    
        def handle(self):
            self.response.payload.cust_name = 'Alexis Abramov'
    
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    from zato.server.service import Service
    
    class MyService(Service):
        def handle(self):
            response = self.invoke('invoking.my-service2', as_bunch=True)
            self.logger.info(response.my_response_elem.cust_name)
    
    class MyService2(Service):
        class SimpleIO:
            input_required = ('cust_id',)
            output_required = ('cust_name',)
            response_elem = 'my_response_elem'
    
        def handle(self):
            self.response.payload.cust_name = 'Alexis Abramov'
    
  • Services that don't use SIO will create string responses that will have to be parsed manually.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    # stdlib
    from json import dumps, loads
    
    # Zato
    from zato.server.service import Service
    
    class MyService(Service):
        def handle(self):
            response = self.invoke('invoking.my-service2')
            response = loads(response) # Convert the string response to a JSON doc
            self.logger.info(response['cust_name'])
    
    class MyService2(Service):
        def handle(self):
            cust_data = {'cust_name': 'Alexis Abramov'}
            self.response.payload = dumps(cust_data)
    
  • When invoking a service asynchronously you don't know when its response will be produced nor what server the service will be executed on even though on the source-code level they both may be defined in the very same Python module as in the example below.

    Hence, the response from an asynchronous invocation is the CID the service which is being invoked will receive.

    You can use the CID returned to correlate asynchronous responses when they are available - how it's performed is up to your application.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    from zato.server.service import Service
    
    class MyService(Service):
        def handle(self):
            cid = self.invoke_async('invoking.my-service2')
            self.logger.info('Got CID {}'.format(cid))
    
    class MyService2(Service):
        def handle(self):
            self.logger.info('My CID is {}'.format(self.cid))
    
    INFO - Got CID K117816978965978292208766150986347804459
    INFO - My CID is K117816978965978292208766150986347804459