The built-in cache

Store data that every service on every server sees immediately.

Every service has access to a built-in cache via self.cache. It is the server-wide Redis connection - self.cache, self.redis and self.kvdb are three names for the same object, so everything Redis offers is available and the examples below cover the caching patterns specifically.

There is one cache, shared by all services and all servers of an environment, which is what makes it useful for coordination - a value stored by one service is immediately visible to every other one, on every server.

Set and get a value

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

# Zato
from zato.server.service import Service

class MyService(Service):

    def handle(self):

        # Store a value
        self.cache.set('my-key', 'my-value')

        # Read it back - values come back as bytes
        value = self.cache.get('my-key')
        value = value.decode('utf8')

        self.logger.info(value)
INFO - my-value

Set a value with expiry

The ex parameter is the expiry in seconds - after that, the key is gone and get returns None.

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

# Zato
from zato.server.service import Service

class MyService(Service):

    def handle(self):

        # Store a value that expires in 60 seconds
        self.cache.set('session-token', 'abc123', ex=60)

        # Read it back - returns the value if not expired, None otherwise
        value = self.cache.get('session-token')

        self.logger.info(value)
INFO - b'abc123'

Cache a dictionary

Dictionaries and lists are serialized to JSON on the way in and parsed on the way out.

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

# stdlib
from json import dumps, loads

# Zato
from zato.server.service import Service

class MyService(Service):

    def handle(self):

        user_data = {
            'username': 'joe',
            'email': 'joe@example.com',
            'roles': ['admin', 'editor'],
        }

        # Store a dict with a 5-minute expiry
        self.cache.set('user:123', dumps(user_data), ex=300)

        # Read it back as a Python dict
        cached = self.cache.get('user:123')
        cached = loads(cached)

        self.logger.info(cached['username'])
        self.logger.info(cached['roles'])
INFO - joe
INFO - ['admin', 'editor']

Delete a key

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

# Zato
from zato.server.service import Service

class MyService(Service):

    def handle(self):

        self.cache.set('temp-key', 'temp-value')

        # Delete the key
        self.cache.delete('temp-key')

        # Returns None because the key was deleted
        value = self.cache.get('temp-key')

        self.logger.info(value)
INFO - None

Check whether a key exists

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

# Zato
from zato.server.service import Service

class MyService(Service):

    def handle(self):

        self.cache.set('flag-key', '1', ex=300)

        if self.cache.exists('flag-key'):
            self.logger.info('Key is present')

        self.cache.delete('flag-key')

        if not self.cache.exists('flag-key'):
            self.logger.info('Key is gone')
INFO - Key is present
INFO - Key is gone

Cache miss

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

# Zato
from zato.server.service import Service

class MyService(Service):

    def handle(self):

        # Returns None when the key does not exist
        value = self.cache.get('my-key')

        if value is None:
            self.logger.info('Key not found, fetching from source ..')
            value = 'value-from-source'
            self.cache.set('my-key', value, ex=120)

        self.logger.info(value)
INFO - Key not found, fetching from source ..
INFO - value-from-source

Set only if absent

With nx=True, the value is stored only when the key does not exist yet, and the call tells you whether it was this call that stored it. Because the cache is shared, this is a single-winner primitive across all services and servers - only one of any number of concurrent callers gets True.

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

# Zato
from zato.server.service import Service

class MyService(Service):

    def handle(self):

        # Only one concurrent caller will store the value and see True
        is_winner = self.cache.set('daily-report-lock', '1', ex=3600, nx=True)

        if is_winner:
            self.logger.info('This invocation runs the report')
        else:
            self.logger.info('Another invocation is already running it')

Beyond caching

Since self.cache is a full Redis client, lists, hashmaps, counters and transactions are all available too - the Redis examples page shows them, and for locking across servers the platform has purpose-built distributed locks. Whole channel responses can also be cached declaratively, without any code, with response caching.

See also

FeatureWhat it does
RedisLists, hashmaps and counters behind the same object
Distributed locksCoordinate work across servers beyond the nx pattern
Response cachingCache whole channel responses with no code

Learn more