Among Amazon’s multiple cloud offerings is “SimpleDb” – a cloud database service
where the data is hosted on Amazon servers. SimpleDb is not a relational database;
it stores items in what Amazon refers to as a Domain.
Each “item” has key-value pairs called attributes, along with a Guid unique key.
You could probably think of a Domain as a kind of big Dictionary of whatever
type you are working with. Since there are no “tables” as in the sense of say,
SQL Server, there are no relations between tables or joins.
However, the Amazon SimpleDb service provides an API that includes a Select syntax
that is remarkably similar to the SQL Statements you are used to writing, so
the learning curve is extremely easy. Many common SQL keywords are available,
so, for example, you could write a query using the “INTERSECT” keyword that queries
on multiple conditions.
You start by signing up with Amazon Web Services here, and you get an Application Key and a “Secret key” that you’ll use in your applications.
Amazon charges by what it calls “block usage”, but don’t worry. With moderate
usage you will never get billed. Of course, if you develop and publicly deploy
an Amazon SimpleDb application and it becomes very popular, then you have a financial
decision to make. But for most applications, that’s not a worry. The downloadable
solution that I provide at the end of this article has placeholders in the web.config
appSettings section where you can insert your own keys, and off to the races
you go!
SimpleDb is fast, and Amazon provides both C# and VB.NET libraries that make using
the service very uncomplicated.
I started out by asking myself, “What would I use this for?”. Obviously, yet another
“phonebook” application wouldn’t be very interesting. The answer came pretty
fast – how about a simple CodeSnippet repository where I can store various code
snippets I need to have access to, along with a title and a set of searchable
“tags”, or keywords.
So I started out with a simple ASP.NET app to test the various API Methods. Then,
I reasoned that I’d like to have a Silverlight version. So I created a Silverlight-enabled
WCF service that simply “wraps” the needed API calls. In the solution I’m providing,
there is already a copy of the Amazon.SimpleDb project that Amazon supplies,
so I won’t go into detail on that.
Once you’ve created your project, you need to reference the Amazon.SimpleDb project,
and include the following namespaces:
using Amazon.SimpleDB;
using Amazon.SimpleDB.Model;
using Amazon.SimpleDB.Mock;
using Amazon.SimpleDB.Util;
To use any of the API methods, we first connect to the Service:
string key = ConfigurationManager.AppSettings["AmazonKey"];
string secretKey = ConfigurationManager.AppSettings["AmazonSecretKey"];
var client = new AmazonSimpleDBClient(key, secretKey);
After authentication, we can use the client object to call any of the SimpleDB methods.
The first step in order to use our new Domain is to create it. This is a one – time
operation:
var createRequest = new CreateDomainRequest();
createRequest.DomainName = TextBox1.Text;
CreateDomainResponse response = client.CreateDomain(createRequest);
The response object for each operation will have various useful fields you can capture,
such as the unique identifier for the object. Once the domain is created, we
can begin calling various operations on it. For example, here is a complete "putRequest"
that creates a new CodeSnippet Item and stores it in the cloud:
protected void Button1_Click(object sender, EventArgs e)
{
string key = ConfigurationManager.AppSettings["AmazonKey"];
string secretKey = ConfigurationManager.AppSettings["AmazonSecretKey"];
var client = new AmazonSimpleDBClient(key, secretKey);
var putRequest = new PutAttributesRequest();
var attributes = new List<ReplaceableAttribute>();
attributes.Add(new ReplaceableAttribute().WithName("Title").WithValue(txtTitle.Text));
string allText = txtSnippet.Text;
if (allText.Length > 1024)
{
double numTimes = (double) allText.Length/1024;
// does it have a fraction? add 1 if it does to get the very last chunk <1024
characters
if (numTimes.ToString().Contains("."))
numTimes++;
for (int i = 0; i < numTimes; i++)
{
attributes.Add(
new ReplaceableAttribute().WithName("Snippet" + i.ToSTring()).WithValue(allText.Substring(i, 1024)));
}
}
else
{
attributes.Add(new ReplaceableAttribute().WithName("Snippet").WithValue(allText));
}
string tags = txtTags.Text;
char[] delim = {','};
foreach (string tagName in tags.Split(delim))
{
attributes.Add(new ReplaceableAttribute().WithName("Tags").WithValue(tagName.Trim()));
}
putRequest.ItemName = Guid.NewGuid().ToString();
putRequest.Attribute = attributes;
putRequest.DomainName = "CodeSnippets";
PutAttributesResponse response = client.PutAttributes(putRequest);
Label1.Text = response.ResponseMetadata.RequestId;
}
}
You can see some "funky code" in the above method. It seems that the maximum
length for an attribute is 1024 characters. So, if my code snippet is longer,
I just "chunk it up" into multiple attributes (Snippet0, Snippet1,
etc.). On the way back, it's easy to use .Contains("Snippet") to reassemble
the text. An annoyance, certainly, but not a show-stopper.
And here is an example of doing a Search on stored Snippets using the SQL-like Select
query:
protected void Button1_Click(object sender, EventArgs e)
{
string srch = TextBox1.Text;
string key = ConfigurationManager.AppSettings["AmazonKey"];
string secretKey = ConfigurationManager.AppSettings["AmazonSecretKey"];
var client = new AmazonSimpleDBClient(key, secretKey);
var selectRequest = new SelectRequest();
selectRequest.SelectExpression = "SELECT * FROM CodeSnippets where Title LIKE '%" + srch + "%' OR Tag ='" +
srch + "'";
SelectResponse response = client.Select(selectRequest);
List<Item> items = response.SelectResult.Item;
var displayItems = new List<DisplayItem>();
foreach (Item itm in items)
{
var disp = new DisplayItem();
disp.Id = itm.Name;
foreach (Attribute att in itm.Attribute)
{
if (att.Name == "Title")
{
disp.Title = att.Value;
}
if (att.Name == "Tags")
disp.Tags += att.Value + " ";
if (att.Name.Contains("Snippet"))
disp.Snippet += att.Value + " ";
}
displayItems.Add(disp);
}
GridView1.DataSource = displayItems;
GridView1.DataBind();
}
You can scale up to millions of items with this ultra-simple cloud database service.
And if you decide later that you want to add additional attributes (think: "columns")
all you have to do is modify your putRequest and everything will work out fine.
The final step in my SimpleDB experiment was to implement the Silverlight-enabled
WCF service and add a SilverLight 3 Navigation app to the solution. The service
is ultra-simple:
using System;
using System.Collections.Generic;
using System.Configuration;
using System.ServiceModel;
using System.ServiceModel.Activation;
using Amazon.SimpleDB;
using Amazon.SimpleDB.Model;
using Attribute=Amazon.SimpleDB.Model.Attribute;
namespace CodeSnippets
{
[ServiceContract(Namespace = "CodeSnippetService")]
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
public class CodeSnippetService
{
[OperationContract]
public CreateDomainResponse CreateDomain(string domainName)
{
string key = ConfigurationManager.AppSettings["AmazonKey"];
string secretKey = ConfigurationManager.AppSettings["AmazonSecretKey"];
var client = new AmazonSimpleDBClient(key, secretKey);
var createRequest = new CreateDomainRequest();
createRequest.DomainName = domainName;
CreateDomainResponse response = client.CreateDomain(createRequest);
return response;
}
[OperationContract]
public List Search(string searchTerm)
{
string key = ConfigurationManager.AppSettings["AmazonKey"];
string secretKey = ConfigurationManager.AppSettings["AmazonSecretKey"];
var client = new AmazonSimpleDBClient(key, secretKey);
var selectRequest = new SelectRequest();
selectRequest.SelectExpression = "SELECT * FROM CodeSnippets where Title LIKE '%" + searchTerm +
"%' OR Tag ='" + searchTerm + "'";
SelectResponse response = client.Select(selectRequest);
List items = response.SelectResult.Item;
var displayItems = new List();
foreach (Item itm in items)
{
var disp = new DisplayItem();
disp.Id = itm.Name;
foreach (Attribute att in itm.Attribute)
{
if (att.Name == "Title")
{
disp.Title = att.Value;
}
if (att.Name == "Tags")
disp.Tags += att.Value + " ";
if (att.Name.Contains("Snippet"))
disp.Snippet += att.Value + " ";
}
displayItems.Add(disp);
}
return displayItems;
}
[OperationContract]
public string PutRequest(string title, string codeSnippet, string tags)
{
string key = ConfigurationManager.AppSettings["AmazonKey"];
string secretKey = ConfigurationManager.AppSettings["AmazonSecretKey"];
var client = new AmazonSimpleDBClient(key, secretKey);
var putRequest = new PutAttributesRequest();
var attributes = new List();
attributes.Add(new ReplaceableAttribute().WithName("Title").WithValue(title));
string allText = codeSnippet;
if (allText.Length > 1024)
{
double numTimes = (double) allText.Length/1024;
// does it have a fraction? add 1 if it does to get the very last chunk <1024
characters
if (numTimes.ToString().Contains("."))
numTimes++;
for (int i = 0; i < numTimes; i++)
{
attributes.Add(
new ReplaceableAttribute().WithName("Snippet" + i).WithValue(allText.Substring(i, 1024)));
}
}
else
{
attributes.Add(new ReplaceableAttribute().WithName("Snippet").WithValue(allText));
}
string thetags = tags;
char[] delim = {','};
foreach (string tagName in thetags.Split(delim))
{
attributes.Add(new ReplaceableAttribute().WithName("Tags").WithValue(tagName.Trim()));
}
putRequest.ItemName = Guid.NewGuid().ToString();
putRequest.Attribute = attributes;
putRequest.DomainName = "CodeSnippets";
PutAttributesResponse response = client.PutAttributes(putRequest);
return response.ResponseMetadata.RequestId;
}
[OperationContract]
public List DisplayResults(string searchTerm)
{
string srch = searchTerm;
string key = ConfigurationManager.AppSettings["AmazonKey"];
string secretKey = ConfigurationManager.AppSettings["AmazonSecretKey"];
var client = new AmazonSimpleDBClient(key, secretKey);
var selectRequest = new SelectRequest();
selectRequest.SelectExpression = "SELECT * FROM CodeSnippets where Title LIKE '%" + srch +
"%' OR Tags LIKE '%" + srch + "%'";
SelectResponse response = client.Select(selectRequest);
List items = response.SelectResult.Item;
var displayItems = new List();
foreach (Item itm in items)
{
var disp = new DisplayItem();
disp.Id = itm.Name;
foreach (Attribute att in itm.Attribute)
{
if (att.Name == "Title")
{
disp.Title = att.Value;
}
if (att.Name == "Tags")
disp.Tags += att.Value + " ";
if (att.Name.Contains("Snippet"))
disp.Snippet += att.Value + " ";
}
displayItems.Add(disp);
}
return displayItems;
}
}
}
You can download the entire solution including the Silverlight 3 Client app here.