Extreme Application Platform Tutorial

  Search Here
Searching GigaSpaces XAP/EDG 6.0 Documentation

                                               

This page is specific to:
GigaSpaces 6.0

If you're interested in another version, click it below:
GigaSpaces 6.5

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.