For this example, let's develop a simple service that processes some data and produces a response. Then, we'll convert it to a distributed service.
First, let's create a simple service. For this example, let's create one that returns us an array of strings representing the Happy Birthday song with someone's name embedded in it.
class Service(object): def happy_birthday(self, name): results = [] for i in range(4): if i == 2: results.append("Happy Birthday Dear %s!" % name) else: results.append("Happy Birthday to you!") return results
Our service isn't too elaborate. Instead of printing the data directly to screen, it collects it together and returns it to the caller. This allows us the caller to print it, test it, store it, or do whatever it wants with the result. In the following screen text, we see a simple client taking the results and printing them with a little formatting inside the Python shell.
As we can see, we have defined a simple service, and can call it directly. In our case, we are simply joining the list together with a newline character, and printing it to the screen.
Let's define a simple IoC container that will create an instance of our service.
from springpython.config import * from simple_service import * class HappyBirthdayContext(PythonConfig): def __init__(self): PythonConfig.__init__(self) @Object def service(self): return Service()
Now let's write a client script that will create an instance of this IoC container, fetch the service, and use it.
from springpython.context import * from simple_service_ctx import * if __name__ == "__main__": ctx = ApplicationContext(HappyBirthdayContext()) s = ctx.get_object("service") print " ".join(s.happy_birthday("Greg"))
Running this client script neatly creates an instance of our IoC container, fetches the service, and calls it with the same arguments shown earlier.
To make these changes, we are going to split up the application context into two different classes: one with the parts for the server and one with parts for the client.
While there is no change to the API, we do have to slightly modify the original client script, so that it imports our altered context file.
First, let's publish our service using Pyro by making some small changes to the IoC configuration.
from springpython.config import * from springpython.remoting.pyro import * from simple_service import * class HappyBirthdayContext(PythonConfig): def __init__(self): PythonConfig.__init__(self) @Object def target_service(self): return Service() @Object() def service_exporter(self): exporter = PyroServiceExporter() exporter.service_name = "service" exporter.service = self.target_service() return exporter
We have renamed the service
method to target_service
, to indicate it is the target of our export proxy.
We then created a PyroServiceExporter
. This is Spring Python's out-of-the-box solution for advertising any Python service using Pyro. It handles all the details of starting up Pyro daemon threads and registering our service.
Pyro requires services to be registered with a distinct name, and this is configured by setting the exporter's service_attribute
attribute to "service". We also need to give it a handle on the actual service object with the export's service
attribute. We do this by plugging in target_service
.
127.0.0.1
, but this can be overridden by setting the exporter's service_host
attribute 7766
, but this can be overridden by setting the exporter's service_port
attributefrom springpython.context import * simple application, converting into distributed applicationwithout, changing the clientfrom simple_service_server_ctx import * if __name__ == "__main__": ctx = ApplicationContext(HappyBirthdayContext()) ctx.get_object("service_exporter")
Now let's start up our server process.
from springpython.config import * from springpython.remoting.pyro import * class HappyBirthdayContext(PythonConfig): def __init__(self): PythonConfig.__init__(self) @Object def service(self): proxy = PyroProxyFactory() proxy.service_url="PYROLOC://127.0.0.1:7766/service" return proxy
We define Pyro client using Spring Python's PyroProxyFactory
, an easy to configure Spring Python proxy factory that handles the task of using Pyro's APIs to find our remote service. And by using the IoC container's original name of service service
embedded in the URL (PYROLOC://127.0.0.1:7766/service
), our client script won't require any changes.
from springpython.context import * from simple_service_client_ctx import * if __name__ == "__main__": ctx = ApplicationContext(HappyBirthdayContext()) s = ctx.get_object("service") print " ".join(s.happy_birthday("Greg"))
Unfortunately, nothing is printed on the screen from the server side. That is because we don't have any print statements or logging. However, if we go and alter our original service at simple_service.py
, we can introduce a print statement to verify our service is being called on the server side.
Let's restart it and call the client script again.
This shows that our server code is being called from the client. And it's nicely decoupled from the machinery of Pyro.
Does this example appear contrived? Sure it does. However, the basic concept of accessing a service from a client is not.
Instead of our Happy Birthday service, this could be the data access layer of an application, an interface into an airline flight reservation system, or access to trouble ticket data offered by an operations center.
Our example still has the same fundamental concepts of input arguments, output results, and client-side processing after the fact.
We took a very simple service, and by serving it up through an IoC container, it was easy to wrap it with a Pyro exporter without our application realizing it. The IoC container, as demonstrated throughout this book, opens the door to many options. Now, we see how it lends itself to exposing our code as a Pyro service. This concept doesn't stop with Pyro. This same pattern can be applied to export services for other remoting mechanisms. By being able to separate the configuration from the actual business logic, we can yet again apply a useful and practical service without having to rewrite the code to work with the service.