Creating a microservice

Basic example

In order to create a simple microservice, you have to subclass the gemstone.MicroService base class:

class HelloWorldService(MicroService):
    name = "hello.world.service"
    host = "127.0.0.1"
    port = 5000

    @public_method
    def say_hello(self, name):
        return "hello {}".format(name)

    @private_api_method
    def say_private_hello(self, name):
        return "this is secret: hello {}".format(name)

    def api_token_is_valid(self, api_token):
        return api_token == "hello_world_token"


if __name__ == '__main__':
    service = HelloWorldService()
    service.start()

After you created your service, run the script that contains it and enjoy.

Exposing public methods

Public methods can be exposed by decorating them with the gemstone.public_method() decorator

class MyMicroService(MicroService):

    # stuff

    @public_method
    def exposed_public_method(self):
        return "it works!"

    # more stuff

Exposing private methods

In order to expose private methods, we have to decorate them with the gemstone.private_api_method(). These methods can be accessed only by providing a valid Api Token with the request. In addition, we must override the gemstone.MicroService.api_token_is_valid() method to implement the token validation logic

class MyMicroService(MicroService):

    # stuff

    @private_api_method
    def exposed_private_method(self):
        return "it works!"

    def api_token_is_valid(self, api_token):
        return api_token == "correct_token"

    # more stuff

Customize the microservice

We can define various specifications for our microservice. The following class attributes can be overridden to customize the behavior of our microservice.

Required attributes

  • gemstone.MicroService.name is required and defines the name of the microservice. MUST be defined by the concrete implementation, otherwise an error will be thrown at startup

Specifying different host, port and location

  • gemstone.MicroService.host - specifies the address to bind to (hostname or IP address). Defaults to 127.0.0.1.

  • gemstone.MicroService.port - an int that specifies the port to bind to. Defaults to 8000

  • gemstone.MicroService.endpoint - a string representing the url where the service api will be accessible. Defaults to "/api", so by default, the service will be accessible at http://{host}:{port}/api.

  • gemstone.MicroService.accessible_at - a string representing a http(s) address specifying a custom location where the service can be found. If at least one service registry is configured, the service will send this value to it so that other services can access at the specified location.

    Example: "http://2a330155abfc.myservice.com/workers/api"

    For example, it is useful when the service runs behind a load balancer and the gemstone.MicroService.accessible_at attribute will point to the address of the load balancer, so that when another service queries the registry for this service, it will access the load balancer instead.

Event dispatching

Other options

  • gemstone.MicroService.validation_strategies - a list of validation strategy instances that will be used to extract the api token that will be forwarded to the MicroService.api_token_is_valid method. Defaults to [HeaderValidationStrategy(header="X-Api-Token", template=None)]

    See Token validation strategies for more details, available options and how to implement custom validation strategies

    If multiple strategies are specified, they will be run in the order they are defined until the first one extracts a value which is not None.

    In order to interact with a service that uses a validation strategy, we have to specify the proper arguments in the gemstone.RemoteService constructor (See the class definition for more info on this).

    New in version 0.3.0.

  • gemstone.MicroService.max_parallel_blocking_tasks - the number of threads that will handle blocking actions (function calls). Defaults to os.cpu_count().

Adding web application functionality

There might be situations when we want to extend the functionality of the microservice so that it will display some stats on some pages (or other scenarios). This library provides a way to quickly add behaviour that is not API-related.

  • gemstone.MicroService.static_dirs - a list of (str, str) tuples that represent the URL to which the static directory will be mapped, and the path of the directory that contain the static files. For example, if the directory /home/user/www/static contains the file index.html, and we specify the static dir attribute with the value [("/static", "/home/user/www/static")], the service will serve index.html at the URL /static/index.html.

  • gemstone.MicroService.extra_handlers - a list of tuples of URLs and Tornado request handlers to be included in the service.

    Note

    Make sure that no other handle overwrites the endpoint of the service.

  • gemstone.MicroService.template_dir - a directory where templates will be searched in, when, in a custom handler we render a template via tornado.web.RequestHandler.render().

Periodic tasks

  • gemstone.MicroService.periodic_tasks - a list of function - interval (in seconds) mappings that schedules the given function to be executed every given seconds

    def periodic_func():
        print("hello there")
    
    class MyService(MicroService):
    
        # stuff
    
        periodic_tasks = [(periodic_func, 1)]
    
        # stuff
    

    In te above example, the periodic_func will be executed every second.

    Note

    There might be a little delay in the execution of the function, depending on the main event loop availability. See the Tornado documentation on PeriodicCallback for more details.

    Note

    If you want to pass parameters to a function, you can use the functools.partial() to specify the parameters for the function to be called with.

Using a service registry

A service registry is a remote service that keeps mappings of service names and network locations, so that each microservice will be able to locate another one dynamically. A service can be a service registry if it exposes via JSON RPC a ping(name, url) method and a locate_service(name) method.

  • gemstone.MicroService.service_registry_urls - a list of URLS where a service registry is located and accessible via JSON RPC.

    service_registry_urls = ["http://registry.domain.com:8000/api", "http://registry.domain2.com"]
    

    On service startup, a ping will be sent to the registry, and after that, a ping will be sent periodically.

  • gemstone.MicroService.service_registry_ping_interval - the interval (in seconds) when the service will ping the registry. Defaults to 30 seconds.

    service_registry_ping_interval = 120  # ping every two minutes
    

Generating a command-line interface

See gemstone.MicroService.get_cli() for more details.