MindTouch Developer Center > Dream > Tutorials > Creating a Magic 8-Ball service

Creating a Magic 8-Ball service

In this tutorial, we'll learn how to build a simple Magic 8-ball service.  Its purpose is to return a random answer whenever it gets invoked.  Usage is very straightforward: ask a question out loud, then enter the 8-balls' URI into your favorite browser, and get the answer you were looking for!

This tutorial shows:

  • What is needed to get started.
  • How to create your very first Dream service.
  • What features are and how they are invoked.
  • How to configure and launch your service.

Getting Started

Let's begin by creating a new project.  Or you can dowload the source files below!

The first thing we need to do is add a reference to mindtouch.dream.dll.  This is the assembly where all the required classes are defined.  Then we need to add a using statement to our source file.

using MindTouch.Dream;

Now, let's create a namespace for it.  We'll also define Yield as a shorthand.

namespace MindTouch.Dream.Samples {
    using Yield = System.Collections.Generic.IEnumerator<IYield>;
}

OK.  That's it!  We're ready to write our first service.

Creating a Dream Service

A Dream service can be written one of two ways: the hard way or the easy way.  The hard way would be to derive our service class from IDreamService and implement every method and property by hand.  The easy way is to derive our service class from DreamService, which implements all the necessary default behavior.  Let's do the latter!

public class EightBallService : DreamService {
}

Voilá! We now have a Dream service.  Well, almost.  We're missing one crucial part: it's blueprint.  A blueprint is an XML-based description of the service used by the Dream runtime to manage it. It contains information about the name, type, copyright, features, and where to find documentation about it.  Again, we are faced with making a difficult choice: either we create the XML document by hand, or we use attributes to have the Dream runtime generate it for us on the fly.  We already took once the easy way out, so doing it again won't really matter.  Attributes it is!

[DreamService("Dream Tutorial 8-Ball", "Copyright (c) 2006, 2007 MindTouch, Inc.",
    Info = "http://doc.opengarden.org/Dream/Samples /8-Ball",
    SID = new string[] { "http://services.mindtouch.com/dream/tutorial/2007/03/8ball" }
)]

The DreamService attributes has four parts of which only the first two are mandatory:

  • Name: "Dream Tutorial 8-Ball"
    This is the human readable name of the service.  It is only used for display purposes.
  • Copyright: "Copyright (c) 2006, 2007 MindTouch, Inc."
    This is the copyright attribution.
  • Info: "http://doc.opengarden.org/Dream/Tutorials/8-Ball"
    This is a URI where to find documentation about the service.  In this case, it refers to this page.
  • SID: "http://services.mindtouch.com/dream/.../2007/03/8ball"
    This is the serviced identifier (or SID).  It uniquely identifies the service blueprint.

A blueprint contains a lot more information than just these four fields, but the rest is generated through reflection and other attributes.

Adding a Feature

Finally, we need to add a feature to our service. "Feature" is the word used in Dream to describe a service entry point, similar to a method on an object.  A feature is defined by its pattern, which captures two parts:

  • Verb: The HTTP verb to which it will respond to (e.g. GET, PUT, etc.).
  • Suffix: The path with optional path parameters (e.g. collection/{item}).

Both parts are combined by a colon, resulting in a pattern that might look like "GET:collection/{item}".  Each feature may have one or more patterns.  The blueprint of a service lists all its features.  Since we didn't create a blueprint document, we'll use the DreamFeature attribute to specify that our method is a feature.

[DreamFeature("GET:", "Returns a random 8-ball message")]
public Yield GetAnswer(DreamContext context, DreamMessage request, Result<DreamMessage> response) {
}

The DreamFeature attribute has two parts:

  • Patttern: "GET:"
    The verb and path of the feature.  In this case, our feature response to the GET verb and has no suffix.
  • Description: "Returns a random 8-ball message"
    A human-readable description of the feature.

The method associated with the feature must return an enumerator and take exactly three arguments: a DreamContext, a DreamMessage, and a TaskYield<DreamMessage>.  The enumerator return is automatically generated by the compiler when using the yield keyword, such as yield break.  The DreamContext holds information specific to the current request.  The DreamMessage contains information sent by the client.  The TaskYield<DreamMessage> is a communication conduit to respond to the client.

Providing Our "Magic" Behavior

Ok, it's time to add the Magic 8-Ball behavior to our code.  For this, we'll create two static member variables: _random and _answers.

private static Random _random = new Random();

private static string[] _answers = new string[] {
    "Reply hazy, try again", "Without a doubt", "My sources say no", "As I see it, yes" 
};

The first one (_random) allows us to generate random numbers.

The second one (_answers) captures our list of possible answers.

Last, we need to change our GetAnswer method to pick a random answer from the list and send it back.

public Yield GetAnswer(DreamContext context, DreamMessage request, Result<DreamMessage> response) {

    // compute a random number between 0 and the number of responses we have
    int index = _random.Next(_answers.Length);

    // send our response back to the requestor
    DreamMessage message = DreamMessage.Ok(MimeType.TEXT, "The 8-ball says: " + _answers[index]);
    response.Return(message);
    yield break;
}

Let's look at these lines in a little bit more detail:

  1. We generate a random integer between 0 and the length of our answer array.
  2. We create a DreamMessage to hold our response.  In this case, it will be a simple text string.
  3. We use the TaskYield<DreamMessage> conduit to send back our response.
  4. Finally, we indicate we're done with the yield break instruction.

That's it!  We now have put in place all the piece required to have a functional service.

Putting the Pieces Together

Let's recap and see what it looks like all put together,

namespace MindTouch.Dream.Samples {
    using Yield = System.Collections.Generic.IEnumerator<IYield>;

    [DreamService("Dream Tutorial 8-Ball", "Copyright (c) 2006, 2007 MindTouch, Inc.",
        Info = "http://doc.opengarden.org/Dream/Samples/8-Ball",
        SID = new string[] { "http://services.mindtouch.com/dream/tutorial/2007/03/8ball" }
    )]
    public class EightBallService : DreamService {
        private static System.Random _random = new System.Random();
        private static string[] _answers = new string[] {
            "Reply hazy, try again", "Without a doubt", "My sources say no", "As I see it, yes" 
        };

        [DreamFeature("GET:", "Returns a random 8-ball message")]
        public Yield GetAnswer(DreamContext context, DreamMessage request, Result<DreamMessage> response) {
            response.Return(DreamMessage.Ok(MimeType.TEXT, "The 8-ball says: " + _answers[_random.Next(_answers.Length)]));
            yield break;
        }
    }
}

We're ready to compile the service and try it out.  No additional coding is needed.

Compile the service into a .dll from the .cs file using the csproj file or Makefile and copy over the files from your dream/redist folder.

Running the Service

Finally, let's run our service.  For this, we need to do two things:

  1. Register its blueprint.
  2. Create an instance.

Fortunately, we don't need to program to do this.  We only need to create an XML script and pass it to mindtouch.host.exe.

Let's create an XML script called 8ball.startup.xml with the following contents:

<script>
  <!-- register the blueprint for our 8-ball service -->
  <action verb="POST" path="/host/load?name=dream.sample.8ball" />

  <!-- instantiate it -->
  <action verb="POST" path="/host/services">
    <config>
      <path>8ball</path>
      <sid>http://services.mindtouch.com/dream/tutorial/2007/03/8ball</sid>
    </config>
  </action>
</script>

The purpose of the script is quite straightforward. Each <action> element represents a web-request.  The first child node of the <action> element becomes the body of the request.  Hence, the effect of the above script could also be produced by using Plug or curl, a popular command line tool for doing HTTP requests.

Now, let's start our service:

mindtouch.host.exe script 8ball.startup.xml

The 8-ball service is now running and can be accessed via any browser:

Tag page

Files 2

FileSizeDateAttached by 
8Ball.cs
8-Ball service sample
2.36 kB15:11, 13 Oct 2007SteveBActions
 8ball.startup.xml
8-Ball service startup XML
368 bytes07:29, 23 Mar 2007SteveBActions
Viewing 1 of 1 comments: view all
A nice client to test REST services is http://rest-client.googlecode.com/
Posted 11:35, 4 Oct 2008
Viewing 1 of 1 comments: view all
You must login to post a comment.
Powered by MindTouch Deki v.8.08.1