Data models

  • Building a data model for your API services revolves around the usage of Python's built-in dataclasses.
  • Dataclasses allow you to define your models in a declarative way that lets you integrate them with your IDE for type-completion or type checking.
  • Furthermore, Zato extends dataclasses to support auto-serialization to and from Python dicts, JSON and OpenAPI definitions.
  • Just like the services, models can be hot-deployed to Zato servers without any restart
  • Models are reusable across services or systems - they are not specific to REST only

Typical usage

  • Each request and response object is a dataclass subclassing a Model class
  • In your code, you work only with high-level business objects such as GetClientRequest and GetClientResponse below
  • Zato automatically serializes the objects from and to JSON, Python dicts or OpenAPI definitions
# -*- coding: utf-8 -*-

# stdlib
from dataclasses import dataclass

# Zato
from zato.server.service import Model, Service

# ###########################################################################

@dataclass(init=False)
class GetClientRequest(Model):
    client_id: int

@dataclass(init=False)
class GetClientResponse(Model):
    name:    str
    email:   str
    segment: str

# ###########################################################################

class GetClient(Service):

    class SimpleIO:
        input  = GetClientRequest
        output = GetClientResponse

    def handle(self):

        # Enable type checking and type completion
        request = self.request.input # type: GetClientRequest

        # Log details of our request
        self.logger.info('Processing client `%s`', request.client_id)

        # Produce our response - in a full service this information
        # will be obtained from one or more databases or systems.
        response = GetClientResponse()
        response.name    = 'Jane Doe'
        response.email   = 'hello@example.com'
        response.segment = 'RNZ'

        # Return the response to our caller
        self.response.payload = response

# ###########################################################################

Let's invoke it:

$ curl http://pubapi:<password>@localhost:17010/zato/api/invoke/client.get-client \
    -d '{"client_id":123}' ; echo
{"name":"Jane Doe","email":"hello@example.com","segment":"RNZ"}
$

Working with lists

  • In the example below, we return a list of objects
  • Dataclasses can be nested in other dataclasses, forming arbitrary hierarchies of objects in a data model
# -*- coding: utf-8 -*-

# stdlib
from dataclasses import dataclass

# Zato
from zato.common.typing_ import list_
from zato.server.service import Model, Service

# ###########################################################################

@dataclass(init=False)
class Phone(Model):
    imei:       str
    owner_id:   int
    owner_name: str

# ###########################################################################

@dataclass(init=False)
class GetPhoneListRequest(Model):
    client_id: int

@dataclass(init=False)
class GetPhoneListResponse(Model):
    phone_list:    list_[Phone]
    response_type: str

# ###########################################################################

class GetPhoneDetails(Service):

    class SimpleIO:
        input  = GetPhoneListRequest
        output = GetPhoneListResponse

    def handle(self):

        # Enable type checking and type completion
        request = self.request.input # type: GetPhoneListRequest

        # Log details of our request
        self.logger.info('Processing client `%s`', request.client_id)

        # Build our response now - in a full service this information
        # would be read from an exteran system or database.

        # Our list of phones to return
        phone_list = []

        # Build the fist phone ..
        phone1 = Phone()
        phone1.imei = '123'
        phone1.owner_id = 456
        phone1.owner_name = 'John Doe'

        # .. the second one ..
        phone2 = Phone()
        phone2.imei = '789'
        phone2.owner_id = 999
        phone2.owner_name = 'Jane Doe'

        # .. populate the container for phones tha we return ..
        phone_list.append(phone1)
        phone_list.append(phone2)

        # .. build the top-level response element ..
        response = GetPhoneListResponse()
        response.response_type = 'RZH'
        response.phone_list = phone_list

        # .. and return the response to our caller
        self.response.payload = response

# ###########################################################################

We can invoke it now with:

$ curl http://pubapi:<password>@localhost:17010/zato/api/invoke/phone.get-phone-list \
    -d '{"client_id":789}'
{"response_type":"RZH",
 "phone_list": [
    {"imei":"123","owner_id":456,"owner_name":"John Doe"},
    {"imei":"789","owner_id":999,"owner_name":"Jane Doe"}]}
$