SDK reference

The Connector SDK contract - fields, lifecycle methods, pools, subscriptions and helper processes.

Zato ships with dozens of connection types out of the box, and the Connector SDK is how you add your own.

Do you have a client library that you would like to use as a first-class connection - an internal library your company maintains, a vendor's Python package, or a client for a custom protocol that only your systems speak? With the SDK, you wrap it once and, from then on, it works like any built-in connection type:

  • Connections are created, edited, deleted and pinged centrally, by name
  • Secrets are stored encrypted and never returned in listings
  • Services access connections through self.out, the same way they access everything else
  • The connector is a regular Python module that you hot-deploy - no server restarts and no changes to the platform itself

The contract

A connector is a subclass of Connector from zato.common.sdk. You give it a type name, declare its configuration fields and implement two methods - everything else is optional.

# -*- coding: utf-8 -*-

# Zato
from zato.common.sdk import Connector, Field

class MyConnector(Connector):

    # Services will access connections of this type as self.out.my_type
    type = 'my_type'

    # Configuration schema
    host = Field.Text()
    port = Field.Int(default=9950)
    api_key = Field.Secret()

    def create_client(self):
        # Build and return the underlying client object
        ...

    def ping(self, client):
        # Confirm the connection works, raise an exception if it does not
        ...

Configuration fields

Fields are class attributes declared with Field types. Their values are stored with each connection definition and delivered to the connector, already resolved, through self.config.

Field typeDescription
Field.TextA text field
Field.IntAn integer field
Field.BoolA boolean field
Field.SecretA secret - stored encrypted and never returned when definitions are read

Each field can declare a default, e.g. Field.Int(default=9950), which applies when a definition does not provide the value.

Methods the framework calls

MethodRequiredDescription
create_clientYesBuilds and returns the underlying client object, called when the connection starts
pingYesConfirms the connection is usable, called when the connection is pinged
on_stopNoCloses the client, called when the definition is deleted or edited
validateNoChecks the client is still usable before each use - the default calls ping, and an exception makes the platform evict the client and reconnect
refresh_credentialsNoRenews expired credentials - called when an invocation raises CredentialsExpired, after which the invocation is retried once
start_process-Provided by the framework, never overridden - starts and supervises a helper process

Invocation methods - get_customer, send, query, whatever your protocol calls for - are plain methods you add freely. The framework never calls them, your services do.

What every invocation gets

When a service calls an invocation method, the platform wraps the call with these behaviors:

  • A watchdog timeout - the definition's timeout field caps how long any call may take, and a single call can override it: conn.get_customer('C1', timeout=5)
  • Validation before use - validate runs first and a client that fails it is evicted and rebuilt before the call proceeds. Pooled connections validate at checkout instead.
  • Credential refresh - an invocation that raises CredentialsExpired triggers refresh_credentials and is retried once
  • Reconnect on loss - an invocation that raises ConnectionLost makes the platform rebuild the connection in the background, with backoff, while the caller gets the exception

Exceptions

zato.common.sdk defines two exceptions your client code raises to tell the platform what went wrong:

ExceptionMeaning and what the platform does
ConnectionLostThe connection is gone - the client is evicted and rebuilt with backoff
CredentialsExpiredThe credentials expired - refresh_credentials runs and the call is retried once

Pooled connections

When connections hold per-connection state - a logon handshake followed by a conversation, one call at a time - subclass PooledConnector instead of Connector. The platform owns a pool of connections, create_client builds one connection each time the pool grows, and invocation methods borrow one per call through get_connection:

# -*- coding: utf-8 -*-

# Zato
from zato.common.sdk import Field, PooledConnector

class MyConnector(PooledConnector):

    type = 'my_type'

    def create_client(self):
        # Build and return one connection - the pool calls this as it grows
        ...

    def send_command(self, command):
        # Borrow a connection for the duration of the with block
        with self.get_connection() as conn:
            return conn.send(command)

PooledConnector adds these to the contract:

MethodRequiredDescription
get_connection-Provided by the framework, never overridden - borrows a connection for a with block
on_get_from_poolNoResets conversational state, runs each time a connection is handed out
on_return_to_poolNoCleans up, runs each time a with block using get_connection ends

A definition's pool_size field caps how many connections its pool holds and connections are built lazily, as the load requires them. The handshake protocols page has a complete, worked example.

Subscribing connections

When the remote side pushes messages on its own - feeds, event streams - subclass SubscribingConnector. It adds one hook to the contract:

MethodRequiredDescription
on_startedNoSubscribes and replays state - runs when the connection first starts and again after every reconnect

The platform watches subscribing connections in the background and, when one goes down, reconnects with backoff and re-runs on_started - so a subscription survives outages without any code beyond the hook itself. The server push page has a complete, worked example.

Helper processes

start_process runs a component next to the server - a Java jar, a .NET assembly, a native binary or a Python module in an interpreter of its own:

def create_client(self):
    process = self.start_process(['java', '-jar', self.config.jar_path, '{port}'])
    return MyClient('127.0.0.1', process.port)

Any {port} placeholder in the command is replaced with a local port allocated for the process. The returned Process object has these members:

MemberDescription
pidThe process ID
portThe local port allocated for the process
is_runningWhether the process is still alive
stopStops the process - first politely, then by force

The process is supervised - if it dies unexpectedly, the platform rebuilds the whole connection with backoff, re-running create_client, and the process always stops together with the connection it belongs to. The foreign runtimes page has a complete, worked example.

Ambient attributes

Every connector instance has these attributes available:

AttributeDescription
self.nameThe name of the connection definition this instance serves
self.configThe resolved values of the fields the class declares
self.clientThe object create_client returned, once the connection has started
self.loggerA logger writing to Zato server logs
self.invokeHands a message over to a service, matching self.invoke in services
self.publishPublishes a message to a pub/sub topic, matching self.publish in services

Many credential sets, one definition

When one definition serves many tenants, each with credentials of their own, services resolve them at runtime with with_config - the platform keeps one client per distinct set of overrides and stops the ones that sit idle for too long:

conn = self.out.crm['My CRM'].with_config(api_key=tenant_api_key)
response = conn.get_customer(customer_id)

Each distinct set of overrides gets a client of its own, built through create_client with the overridden values in self.config, and repeated calls with the same overrides reuse it.

What happens at runtime

When your module is deployed, the platform finds the Connector subclasses in it and registers each one as a new connection type. From that moment:

  • Definitions of the type can be created, edited, deleted and pinged like any other connection
  • Each definition is served by one connector instance with one client, shared by all the services on a server
  • Editing a definition stops the old client through on_stop and builds a new one with the new configuration
  • Redeploying the module updates the type in place and the definitions that are already running keep working
  • After a server restart, definitions start automatically as soon as the module is deployed again at boot

Learn more