Tutorials/simpleservice

From CubeiaWiki

Jump to: navigation, search

Firebase Services is a useful concept to isolate logic and/or extend the functionality provided by the server. A Service can be deployed separately and executes in it's own isolated environment (i.e. class loader). Services can be used to wrap external services (e.g. ideal for SOA/WOA environments) or internal logic that would benefit from being isolated from the game logic. Services should ideally be developed as a reusable component.

Users can send packets directly to a Service if configured as such. This allows you to extend Firebase with any type of functionality and might prove to be more bandwidth and latency efficient compared to regular HTTP calls to a WOA Service.

Example of natural cases for a Service would be:

  • Calls to wallet
  • Event publishing/subcription (JMS/MQ)
  • Calls to user database
  • Friends lists

An intrinsic advantage of wrapping logic in Services is that they are easily replaced when they are deployed separately. E.g. let's say you have an Wallet module that handles all money transaction. In a production/staging environment you have a wallet-service deployed that routes all money calls to a remote service over HTTP. But on a local developer machine you may want to have a wallet-service deployed that always returns OK for all calls in order to reduce the complexity to setup and run a developer environment.

Contents

Scope

This tutorial will show you how to write a simple Service and how to access it.

Prerequisites

This tutorial assumes that you have knowledge about Maven and Firebase. It also assumes that you are familiar with the basic definitions of the Firebase platform.

Setup

Create a Service project by using the Maven archetype.

mvn archetype:generate -DarchetypeGroupId=com.cubeia.tools -DarchetypeArtifactId=firebase-service-archetype -DarchetypeVersion=1.8.0 -DarchetypeRepository=http://m2.cubeia.com/nexus/content/groups/public

You will get some prompts for Maven attributes, answer them as follows:

Define value for groupId: : com.test
Define value for artifactId: : service-tutorial
Define value for version:  1.0-SNAPSHOT: :
Define value for package:  com.test: :

This will create some default resources in a folder called service-tutorial. Open the created project in your favorite IDE, I will use Eclipse. Import the project to your IDE as a Maven project (in Eclipse, File -> Import... then select Maven2 and browse to the service-tutorial folder).

The resources in your project are:

ServiceContract.java

Location: src/main/java/com.test.api.ServiceContract
This is the Java interface for the service. This will be exported in Firebase to all other resources.

ServiceImpl.java

Location: src/main/java/com.test.impl.ServiceImpl
This is the Java implementation of the interface for the service. This is created with empty stub-method.

service.xml

Location: src/main/resources/firebase/META-INF/service.xml
This is the deployment descriptor for Firebase. It must be located in this location since it will be used by the packaging Maven task.

Here is how the project looks in Eclipse:
File:service-project.png

Adding Logic

To add Service methods you just add the method to the interface and then to the implementation. We will add a simple get system time method.

Add to the interface:

String getSystemTime();

Add to the implementation:

@Override
public String getSystemTime() {
    return new Date().toString();
}

The getSystemTime() method will return the current system time in the default String format. Simple enough.

Accessing From a Game

To access the Service from your game you simply fetch the Service from the ServiceRegistry using the interface class and call methods as defined in the interface.

Example, in your init method in your game implementation:

public void init(GameContext context) throws SystemException {
    services = context.getServices();
    ServiceContract testService = services.getServiceInstance(ServiceContract.class);
    String systemTime = testService.getSystemTime();
}

Accessing From Client

Events from a client can be routed to a service. Let's expose the tutorial service to clients so that a client can request the system time directly using the regular TCP socket communication channel.

Let the ServiceContract interface extend the interface RoutableService. It should look like this:

public interface ServiceContract extends Contract, RoutableService { 
    String getSystemTime();
}

In the ServiceImpl we add the added interface methods and some simple implementation like below.

private ServiceRouter router;

...

@Override
public void setRouter(ServiceRouter router) {
    this.router = router;
}

@Override
public void onAction(ServiceAction action) {
    // Always respond with system time regardless of action type
    byte[] bytes = getSystemTime().getBytes();
    ClientServiceAction timeResponse = new ClientServiceAction(action.getPlayerId(), action.getSeq(), bytes);
    router.dispatchToPlayer(action.getPlayerId(), timeResponse);
}

That's it. The Service should now be accessible directly by clients. If we look at the generated deployment descriptor it looks like this:

<service auto-start="true">
   <name>service-tutorial</name>
   <public-id>com.test:service-tutorial</public-id>
   <contract>com.test.api.ServiceContract</contract>
   <service>com.test.impl.ServiceImpl</service>
   <dependencies />
   <description />
   <exported>
       <package>com.test.api.-</package>
   </exported>
</service>

The contract attribute can be used as identifier for which Service to route to.

From a client we can then send a ServiceTransportPacket to the com.test.api.ServiceContract Service and we should then get a ServiceTransportPacket with the system time contained in the payload. Below is code example for a Java client.

Sending a ServiceTransportPacket to the example Service:

private void sendSystemTimeRequest() {
    ServiceTransportPacket packet = new ServiceTransportPacket();
    packet.pid = context.getPlayerId();
    packet.seq = seq;
    packet.idtype = (byte)ServiceIdentifier.CONTRACT.ordinal();
    packet.service = "com.test.api.ServiceContract";
    context.getConnector().sendPacket(packet);
    return;
}

Handling incoming ServiceTransportPackets:

@Override
public void visit(ServiceTransportPacket packet) {
    System.out.println("The time on the server is: "+new String(packet.servicedata));
}

Note: The 'seq' attribute in the packet is designed to be used as a sequence number. This can be used by the client to map request packets to response packets. Of course, you may use this as you seem fit - Firebase makes no assumptions regarding 'seq'.

Personal tools