GS6 == "XAP9NET" || GS6 == "XAP9" || GS6 == "XAP9NET")

Extreme Application Platform Tutorial

  Search Here
Searching XAP 6.0 Documentation

                                               

Overview

In the other tutorials, we show how to develop an order management system to run on the GigaSpaces platform, achieving parallel processing of business transactions. We also showed how to deploy a cluster in different topologies, creating an enterprise data grid – using the space as the underlying layer for data (caching) and business transaction processing.

The purpose of this tutorial is to introduce the full concept of SBA – Space Based Architecture, and the way to easily write stateful applications, deploy them and then scale them out in a linear way. In GigaSpaces, this is done with the help of Open Spaces, a framework built on top of GigaSpaces that greatly simplifies development, deployment and scaling, by relying on and using Spring capabilities and configuration. Now, if you don't know Spring, don't worry, you don't have to know Spring to get Open Spaces, but if you do know Spring, you'll probably find yourself in a familiar territory.

About the Application

The application here is, again, an order management system. It is compiled from a set of services that form the entire workflow. In a high-level, the main services are:

  • A feeder service that sends out order events (a buy or sell) with usernames and prices.
  • A validator service that checks new orders by looking for the preloaded account for the specific username – if it doesn't find the account, the order is rejected.
  • A processor service that takes validated orders, and updates the user account with the new balance (unless it is a buy order and there are insufficient funds for that account).

When writing an application with Open Spaces, you will implement Processing Units (PU) that are later deployed to the GigaSpaces Service Grid.

In a nutshell, a processing unit is a container where a set of services run and interact. According to the SOA model, the services are loosely coupled, meaning they don't know about each other and are independent of one another, but as opposed to the known webservices model, services that run within the same processing unit basically run within the same process. This way, an SOA application can run with the lowest latency possible.

The different services interact and share data using a space (as defined in the Javaspaces specification) that can run embedded within the same processing unit; and acts as the transport layer and the data layer, eliminating the need to use a database and a messaging solution.

In this tutorial you will implement 3 different processing units as shown below:

You can probably see that there are more services than we initially mentioned, so lets go over the entire contents of the diagram:

First, our domain model, these are the account and order objects that appear in the diagram inside the space. The space itself is embedded within the Runtime Processing Unit (naming the processing unit is up to you).

The application consists of 3 different processing units, each running its own services:

  • The runtime processing unit includes the embedded and only space in the application, as well as the validator and processor services.
  • The feeder processing unit includes the feeder as well as the AccountDataLoader service that preloads the user accounts into the space – think of it as the initial service of the application that preloads data from the database into the cache. In real applications using GigaSpaces, the DataProvider loads the data from an external data source (that can be a database or any other external application) into the space.
  • The stats processing unit takes care of the statistics of the application. It includes two services, each receives notifications from the space on different events. One service is notified about changes in the account objects, while the other service monitors the flow of orders.

The Application Workflow

The workflow according to the diagram above is described here:

When the feeder PU is loaded, the preloader (from the feeder PU) loads 100 account objects into the space. Then the following cycle occurs:

  1. The OrderFeeder (from the feeder PU) starts writing Orders with status New.
  2. The Validator (from the runtime PU) takes an order with the status New.
  3. The Validator then reads the account with the username of that order from the space.
  4. If the account with the same username is found, the Validator writes back the order to the space with status Pending, otherwise it writes it with status AccountNotFound.
  5. The Processor (from the runtime PU) takes an order with the status Pending.
  6. The Processor then reads the account with the same username as the order from the space, this time with an exclusive read lock, making the account invisible to other processor threads.
  7. The Processor updates the account object in the space with the new balance of that user, according to the order it processed (a Buy order will reduce the balance, a Sell order will increase it).
  8. In case of a Buy order with insufficient funds in the balance of the account, the Processor writes back to the space the order with status InsufficientFunds. Otherwise, the order is written with status Processed.
  9. The AccountCounter (from the statistics PU) receives a notification if an account object is written or updated in the space.
  10. The OrderCounter (from the statistics PU) receives a notification every time an order is written into the space.

Implementation

The process of implementing your application over Open Spaces is quite easy once you follow a few simple steps. One of the benefits of this approach is that you get to implement and test your code on a single machine, indifferent to the final topology, the number of nodes in your cluster and the remoteness of the machines. Once you're finished with the implementation, 90% of your work is done.

Let's review those steps:

  1. Implement your POJO domain model – what data or event object does your application require? This tutorial, as shown in the diagram above has two domain model objects, the order which is an event object and account which is our data object.
  2. Implement the POJO Services – these are the business logic of your application. Those are the services that you later run on top of a processing unit. These objects are really simple POJOs, they do not implement any interface or have to comply with any standard. Instead, using annotations, which you define within your POJO services, you can mark the methods that are used to process events.
  3. Wire everything with the Spring configuration file. The core of the processing unit is its configuration XML file called pu.xml. In this file, you define your Spring beans (the services' objects are some of them), the event handling containers (polling or notify), the configuration of the space you want to access (the space can be embedded within that processing unit, or a remote space), and a few other things that we will show later, but quite a lot of the logic and workflow of your application is actually defined within that XML.
  4. Package and Deploy – there are several ways to deploy a processing unit, either as standalone or onto a Grid Service Container (GSC) which is part of our Service Grid. This tutorial will show you the latter approach, and you can find more information on the Open Spaces documentation in the Open Spaces section.

OK, let's code!

POJO Domain Model

Because our domain model is used throughout the entire application by the different Processing Units, we should define it in a generic place, so we can then include it in each of the PUs.

As you can see, the code is pretty straight forward, what's interesting though, are the annotations used in it:

  • @SpaceClass – marks this class as an Entry that will be written to a space (and then probably read or taken from the space).
  • @SpaceId(autoGenerate = true) – marks the following method as the generator of the unique key for that object type. In this case, the orderID attribute is unique per instance and is automatically generated by the container.
  • @SpaceRouting – marks the attribute according to which routing to the correct space partitions will be done. This is only relevant when we deploy a cluster of spaces in partitioned mode, and objects written to the cluster need to be routed in a certain manner.

Writing POJO Services

Once we're done with the implementation of our domain model, it's time to implement each of the six services described in the above diagram. We will cover each one of them, but first, we want to make the code of the services oblivious to the underlying space so that our code will not be dependent on the GigaSpaces solution.

Solving the dependency issue for the event objects (OrderEvent) is easy, as Open Spaces takes care of the polling and notification operations behind the scenes since it's all configured in the pu.xml file of the Processing Unit. Doing the same with the AccountData objects is not possible as the services have to access those objects according to the application's specific business logic. To solve this, we'll implement a DAO (Data Access Object) that will take care of the space operations.

AccountDataDAO

Let's move on to the services, each described in its own tab below:

Wiring with Spring (PU Configuration)

By now, the implementation of the domain model, DAO, and services is done, but you might have noticed that the picture is not yet complete. The configuration of a space, and types of events that the services should handle are some of the things that are not defined anywhere within the code. Instead, we define all of these in the configuration file (pu.xml) of each processing unit.

Each pu.xml appears below on a different tab.

Full Element Description
<os-core:giga-space-context/>
<os-core:space id="space" url="jini://*/*/spaceOMS"/>
<os-core:giga-space id="gigaSpace" space="space"/>
  • The first element enables the usage of @GigaSpaceContext within the services in the same Processing Unit.
  • The second element defines the space that is accessed by the services in that Processing Unit. The space can either be accessed embedded (as in the Runtime PU) using the url /./spaceOMS, or remote (as in the other two PUs) using the url jini://*/*/spaceOMS. The spaceOMS is just the name of the space, which can be any value but has to be consistent throughout the application.
  • The third element defines an instance of the GigaSpace proxy object, connected to a specific space.
<!-- Defines a local Jini transaction manager. -->
<os-core:local-tx-manager id="transactionManager" space="space"/>
This element defines a transaction manager instance that can be used by the services in that processing unit.
<bean id="accountDataDAO" class="org.openspaces.example.oms.common.AccountDataDAO"/>

<bean id="orderEventValidator" class="org.openspaces.example.oms.runtime.OrderEventValidator">
    <property name="accountDataDAO" ref="accountDataDAO" />
</bean>
Every service or object can be defined as a Spring Bean within the bean element. The first line shows how we define our DAO object for later use. The second definition is of the OrderEventValidator that also includes as a property the reference to the DAO. That property is used for the injection of the DAO using the setAccountDataDAO method within the validator.
<os-events:polling-container id="orderEventValidatorPollingEventContainer" 
		giga-space="gigaSpace">
    <os-events:tx-support tx-manager="transactionManager"/>
    <os-core:template>
        <bean class="org.openspaces.example.oms.common.OrderEvent">
            <property name="status" value="New"/>
        </bean>
    </os-core:template>
    <os-events:listener>
        <os-events:annotation-adapter>
            <os-events:delegate ref="orderEventValidator"/>
        </os-events:annotation-adapter>
    </os-events:listener>
</os-events:polling-container>
A polling container defines a "pull" type of event-handling for a specific type of events. By default, the polling container performs a take operation every 5 seconds, but both the operation and interval can be changed.
  • tx-support sets the transaction manager that is used by the polling container.
  • The template sub-element defines the template for the take operation. In this example, the template is an OrderEvent object with the status attribute set to New.
  • listener refers to the id of the service that is triggered by the event, while the method that handles that event is the one that was marked with the @SpaceDataEvent annotation. Note that behind the scenes, the container takes from the space one Entry that matches the template, and calls the relevant method in the relevant service, with that Entry as a parameter.
<os-events:notify-container id="orderEventNotifyContainer" giga-space="gigaSpace">
	<os-core:template>
        <bean class="org.openspaces.example.oms.common.OrderEvent">
        </bean>
    </os-core:template>
    <os-events:listener>
        <os-events:annotation-adapter>
            <os-events:delegate ref="outputOrderEvent"/>
        </os-events:annotation-adapter>
    </os-events:listener>
	</os-events:notify-container>
Similar to the polling container, the Notify container receives events according to a defined template, but in a "push" mode. An event will occur every time an operation occurs on the space, by default, a write operation.
<os-sla:sla>
    <os-sla:monitors>
        <os-sla:bean-property-monitor 
                name="Written OrderEvent" 
                bean-ref="outputOrderEvent" 
                property-name="orderEventWrittenCounter" />
        <os-sla:bean-property-monitor 
                name="Processed OrderEvent" 
                bean-ref="outputOrderEvent" 
                property-name="orderEventProcessedCounter" />
        <os-sla:bean-property-monitor 
                name="AccountNotFound OrderEvent" 
                bean-ref="outputOrderEvent" 
                property-name="orderEventAccountNotFoundCounter" />
    </os-sla:monitors> 
</os-sla:sla>
Under the SLA element we put the definitions of our SLA, that is the topology of a cluster and failover policies. This element can be defined within the pu.xml as shown here, or by overriding another XML file with the SLA element. We will show how to do this later, but here we show how to define monitors. Monitors are attributes from different services that we want to monitor in the GigaSpaces Management Center.

Building and Packaging

Once everything is ready, we need to build, package and deploy the application.

The following jar files need to be in your classpath, they all reside in the <GigaSpaces Root\lib directory or its subfolders:

  • JSpaces.jar
  • openspaces/openspaces.jar
  • spring/spring.jar
  • jini/jsk-platform.jar
  • jini/jsk-lib.jar

In order to deploy the application, we need to deploy each of the three processing units separately. To do this, every processing unit must be placed in the <GigaSpaces Root>\deploy directory. Every processing unit is actually a folder (whose name is the name of the processing unit later used for deployment) with several subfolders. Here's a typical processing unit directory as it resides under the <GigaSpaces Root>\deploy directory:

As the image shows, under the deploy folder of the product, we've put the oms-feeder folder which is in fact our feeder PU of the application. The folder name states the name of the processing unit.

Under META-INF\spring is our pu.xml file, relevant for the feeder processing unit. Also, directly under the deploy folder is our compiled code, with its appropriate package structure (that's the org folder seen in the image).

Finally, the shared-lib includes all those libraries that are used globally by that processing unit and potentially other processing units. In this case, shared-lib includes a jar file that includes our domain model (account and order) objects as well as the DAO. The same jar is put under the shared-lib folder of each of the processing units in our application.

To summarize the building and packaging process:

  1. Compile your code.
  2. Create a common jar file for your shared libraries.
  3. Create a folder of that Processing Unit that complies with the structure shown in the image, and copy all the necessary files into it accordingly. Give that folder any name you want for that PU.
  4. Copy the created folder into the <GigaSpaces Root>\deploy directory.
  5. Repeat for every processing unit in the application.

Deployment

You are now ready to deploy your application, but first, a short recap:

Our Order Management System application has 3 processing units: Feeder, Runtime and Statistics. The domain model includes an order object and an account object, as well as a data access object to encapsulate account-related operations on the space.

There are several ways to deploy the application and to run a processing unit. A PU can either run in standalone mode within your IDE (for development and testing purposes), or on top of the Service Grid, within SLA driven containers that we like to call GSC (Grid Service Container). In this tutorial, we'll show the latter approach, which is used in production environments.

We will do this is two phases. First, we'll show how to deploy the application with a single space (the one that runs embedded within the runtime processing unit, as shown in the first image above). Second, we'll show how to deploy that same application in a clustered environment, where we partition the data and workload to several spaces, meaning we'll run multiple runtime PUs.

Deployment of a Single Space

  1. Because we want to deploy to the Service Grid, we first need to start it. Running the grid is as easy as running one GSM (Grid Service Manager) from the <GigaSpaces Root\bin and several GSCs (Grid Service Container – SLA-driven container) on top of which we run our processing units. The deployed application will then look like this:


    Even though the image shows one container per processing unit, we can run several processing units on each container.

  2. After starting the GSM, execute:
    <GigaSpaces Root>\bin\gsm.(sh/bat)
  3. Run two GSCs by execute the following line twice:
    <GigaSpaces Root>\bin\gsc.(sh/bat)
  4. Start the GigaSpaces Management Center:
    <GigaSpaces Root>\bin\gs-ui.(sh/bat)

    The Management Center is displayed:



  5. Click the Deployments, Details tab at the left.
    The Deployments, Details tab is displayed:



  6. The two running GSCs are displayed at the bottom of the tab. Both of them are still empty, because no processing units have been deployed.
  7. To deploy a processing unit, click the deploy button ().
    The deployment wizard is displayed:



  8. In the first page of the wizard, click the SBA Application - Processing Unit radio button, and click Next.
    The deployment options page is displayed:



  9. In the Processing Unit name field, type the name of the Processing Unit. This name should be the same as the name of the processing unit directory, located under the <GigaSpaces Root>\deploy directory.
    For example, if you copied the runtime PU folder under the deploy directory with the name oms-runtime, type oms-runtime in the Processing Unit Name field.

    You can specify an alternative name for the processing unit, which will be displayed in the Management Center interface, using the Override PU Name field (the Processing Unit Name field must still match the name of your PU directory). For example, if your Processing Unit Name is oms-runtime, your override name might be something like Order Management Runtime Module.

  10. Click Deploy.
    The deployment status page is displayed:



  11. Wait until the Processing Unit successfully finishes deploying, then click Close.
  12. Now, have another look at the lower side of the Deployment, Details tab. You should see that one of the GSCs contains the Processing Unit you just deployed.
  13. Repeat the deployment process twice more for the other Processing Units (remember, you are deploying the runtime, feeder and statistics processing units).
    At the bottom of the Deployments, Details tab, you should now see three Processing Units deployed in the two running containers:



  14. Remember the os-sla:monitors elements we mentioned? They were defined in the pu.xml of the statistics processing unit. Locate the statistics PU in the containers at the bottom, and double-click its name.
    A visual representation of the monitors is displayed:



    For each processing unit in which you define a monitor, you can easily access that monitor's view by double-clicking the relevant PU's name.

  15. Click the Space Browser tab on the left. In the Grid Tree on the left, click Spaces.
    The Service View on the right updates to show your running spaces:



    (Currently, you should only have one space running in the runtime PU.)
  16. In the Grid Tree on the left, click the Statistics node.
    The Service View on the right updates to show statistics for your running space – how many times different operations were performed on the space:



    The write and take operations shown in the statistics are the events and accounts written to the space and taken from it. The notify operations are notifications sent to the statistics PU.

Partitioned with Synchronized Backup Deployment

As you could see, developing and deploying our application on the grid wasn't too difficult. Following the 4 steps makes the process clear and easy, and with Open Spaces we achieve a lot of abstraction from the code.

So now the application sends events and processes them while using an embedded space to keep the states of the accounts, as well as to deliver events between services, thus using the space as both the data and the messaging layer, and the entire platform is running a full application without the need to integrate any other product.

But how does it scale? Let's say there are more feeders of order events, those events might be coming from many different directions. Eventually, our runtime processing unit that holds the space will become a bottleneck, it will either run out of memory (as all the objects in the space consume memory), or just fully utilize the CPU. So how do we solve this?

We simply scale out, we add more instances of the runtime processing unit on other SLA containers (GSCs) that run on other machines. There are several possible topologies to achieve this scaling, but the most common one is the partitioned-with-backup topology. This means that there are several spaces running, each with a different partition of the entire data, so sticking with our example, imagine two partitions, each with another half of all the user accounts. This way, when an order event comes in, it is automatically routed to the relevant partition according to the username of that order (remember the @SpaceRouting annotation in the OrderEvent class? That's what it is used for).

In order to achieve better reliability and failover capabilities, each partition also has one backup space, to which it replicates its entire contents in a synchronous manner. In case the primary partition goes down, the backup of that partition automatically takes over, continuing from where the failing primary stopped.

It all must seem quite complicated, but it isn't. Once we've accomplished the previous step of implementation and wiring with Spring, it is just a matter of a small difference in the deployment process. Here's how:

Restart the GSM and two GSC as earlier (to keep things clean just shut down whatever you had running earlier).

Now, within the GigaSpaces Management Center (<GigaSpaces Root>\bin\gs-ui.bat/sh), click on the Deployment, Details tab and on the deploy button. Choose again the first option SBA Application - Processing Unit. On the next screen, put the name of the processing unit you want to scale out (because the runtime processing unit is the only one with an embedded space, it makes sense to partition it) as before. But now, choose the cluster schema partitioned, put 2 in Number of Instances and 1 in the Backups fields. Note that the backups value is per partition, meaning in this case we'll have two partitions, each one with a backup. This is how the screen should look:

Note that the deployment will take a few seconds longer as it now deploys 4 spaces (two primaries and two backups). Now deploy the feeder and statistics processing units as before.

Back in the Deployments, Details tab you'll now see 4 instances of the runtime processing unit. Note that each primary is on a different container than its backup:

Finally, click on the Space Browser tab to see the four spaces you've just deployed:

That's it! You've just scaled out your application and can scale it even further by deploying it to as many machines as you want.

What's Next?

Try Another Tutorial
GigaSpaces XAP Help Portal
GigaSpaces EDG Help Portal


IMPORTANT: This is an old version of GigaSpaces XAP. Click here for the latest version.
GigaSpaces 6.0 Documentation Contents (Current Page in Bold)

    Java

    C++

    .NET

    Middleware Capabilities

    Configuration and Management

Add GigaSpaces wiki search to your browser search engines!
(works on Firefox 2 and Internet Explorer 7)

Labels

 
(None)