Project 3 - A Simple Multi-language RPC-based Twitter Service

In this project, you are going to build a simple Twitter client (http://www.twitter.com). Twitter is a social networking site where users can post short messages (up to 140 characters in length), subscribe to other users' tweets, and 'star' tweets to favorite them.

Learning objectives:

Project due: Thursday, March 12th, 2015, 8:00am

Overview

Your twitter implementation will consist of two main components:

Twitter server

The server implements the application logic of the Twitter service. Commands like “subscribe to”, and “get a list of all tweets from users I subscribe to” are implemented here.

How your implement the internals of your server is up to you--we will only test its behavior as expressed through the RPC commands. You may choose whatever language you'd like for your server.

You do not need to keep state across different executions of the server. When the server starts up, it should have no state.

Twitter client

The client application (or anything you choose to write) connects to the server, and represents the behavior of the user (in a real application, it might be running as part of a web service). All it knows how to do is parse commands, send them to the service, and print out results. You may choose whatever language you'd like for your client, as long as it is different from your server.

Twitter client/server interface

Clients interact with the server via Apache Thrift. Each tweet is represented as a Thrift struct:

1. Data structure

struct Tweet {
    1:i64 tweetId,
    2:string handle,
    3:i64 posted,
    4:i32 numStars,
    5:string tweetString
}

The meaning of each field is:

  1. tweetId: each tweet is identified by a unique identifier.
  2. handle: each user is identified by a unique string identifier, starting with an '@' sign. Examples include @aturing, @vneumann, and @adalovelace.
  3. posted: the date/time that this tweet was received by the server (in seconds since 1970)
  4. numStars: each user can 'like' a tweet by giving it a star. This field counts the number of stars received for the tweet. A user should not be able to star a tweet more than once.
  5. tweetString: the contents of the tweet itself. Tweets are strings that are 140 characters or less in length.

2. Creating users

When a user joins the system, they must create a unique handle that they will be known by. We're not considering passwords in this project.

exception AlreadyExistsException {
    1:string user; // the handle that already exists
}

void createUser(1:string handle) throws (1:AlreadyExistsException existsx)

Once a user is created, they exist forever--a user cannot be deleted. If a user alread exists in the system, then an AlreadyExistsException is raised, and the duplicate user is returned.

2. Subscribing/unsubscribing from others users

Users can subscribe (or unsubscribe) to another user's tweets using the following calls:

exception NoSuchUserException {
    1:string user;  // the user that doesn't exist
}

void subscribe(1:string handle, 2:string theirhandle) throws (1:NoSuchUserException userx),
void unsubscribe(1:string handle, 2:string theirhandle) throws (1:NoSuchUserException userx)

Your server should not let a non-existent user (un)subscribe to anything, and a valid user should not be able to subscribe to a non-existent user's tweets. It is OK to subscribe to your own tweets. If a user tries to unsubscribe from a valid user, however they aren't already subscribed to that user, then no error is returned, and it acts just like a 'nop'.

3. Posting tweets

A user can post tweets by using the following call.

exception TweetTooLongException {
    // can't be longer than 140 characters
}

void post(1:string handle, 2:string tweetString) throws
    (1:NoSuchUserException userx, 2:TweetTooLongException longx)

Your implementation should not allow non-existent users to post tweets, and tweets longer than 140 characters should not be allowed.

4. Reading tweets

You can read tweets written by a specific user (identified by handle) using the readTweetsByUser RPC call. The number of tweets that should be returned is specified as part of the call (via the howmany argument). If you use the readTweetsBySubscription call, you will read tweets from users that handle subscribes to (again, limited by the 'howmany' parameter).

list<Tweet> readTweetsByUser(1:string handle, 2:i32 howmany) throws (1:NoSuchUserException userx),

list<Tweet> readTweetsBySubscription(1:string handle, 2:i32 howmany) throws (1:NoSuchUserException userx)

As before, your implementation should not allow reading from non-existent users. If the 'howmany' parameter specifies more tweets than are available (for example, you ask for 100 tweets, but a user has only posted 20), then you should simply return the number available. If howmany is zero or a negative number, you should simply return an empty list.

Tweets should be returned in reverse chronological ordering, meaning that the most recently posted tweets are returned first. For readTweetsBySubscription, you must ensure that tweets are returned in reverse chronological ordering across all subscribed users. What does that mean? Consider that a user Alice has subscribed to both Bob and Oscar:

Now imagine that Bob posts tweets at the following timestamps:

Now imagine that Oscar posts tweets at the following timestamps:

When tweets are read from Alice's subscriptions, they should be returned in the following order:

  1. <Tweet tweetString="oscar_tweet_2", posted=T4, ...>
  2. <Tweet tweetString="bob_tweet_3", posted=T3, ...>
  3. <Tweet tweetString="oscar_tweet_1", posted=T2, ...>
  4. <Tweet tweetString="bob_tweet_2", posted=T1, ...>
  5. <Tweet tweetString="bob_tweet_1", posted=T0, ...>

5. Favoriting/starring tweets

A user can favorite (or 'star') a tweet using the star() call. Your implementation should ensure that only valid users can star a tweet, that only valid tweets can be starred, and your implementation should ensure that a user can only star a tweet at most one time. If a user tries to 'star' a tweet more than once, this call should simply return without error (no need to throw an exception).

exception NoSuchTweetException {
}

void star(1:string handle, 2:i64 tweetId) throws (1:NoSuchUserException userx, 2:NoSuchTweetException tweetx)

6. Complete twitter.thrift file

The complete twitter service is defined by this thrift IDL specification. Note that you may add additional procedures/methods, data structures, etc. However you can not change the type signatures of the RPC calls--they must remain the same. You may optionally add (though not remove or modify) fields in the Twitter structure.

The complete Thrift file:

namespace java edu.ucsd.cse124
namespace py cse124

struct Tweet {
    1:i64 tweetId,
    2:string handle,
    3:i64 posted,
    4:i32 numStars,
    5:string tweetString
}

exception AlreadyExistsException {
    1:string user; // the handle that already exists
}

exception NoSuchUserException {
    1:string user;  // the user that doesn't exist
}

exception TweetTooLongException {
    // can't be longer than 140 characters
}

exception NoSuchTweetException {
}

service Twitter {
    void ping(),

    void createUser(1:string handle) throws
        (1:AlreadyExistsException existsx),

    void subscribe(1:string handle, 2:string theirhandle) throws
        (1:NoSuchUserException userx),

    void unsubscribe(1:string handle, 2:string theirhandle) throws
        (1:NoSuchUserException userx),

    void post(1:string handle, 2:string tweetString) throws
        (1:NoSuchUserException userx, 2:TweetTooLongException longx),

    list<Tweet> readTweetsByUser(1:string handle, 2:i32 howmany)
        throws (1:NoSuchUserException userx),

    list<Tweet> readTweetsBySubscription(1:string handle, 2:i32 howmany)
        throws (1:NoSuchUserException userx),

    void star(1:string handle, 2:i64 tweetId)
        throws (1:NoSuchUserException userx, 2:NoSuchTweetException tweetx)
}

Getting started

Starter code

Starter code is available online: cse124-proj3.tar.gz. This code is based on the in-class exercise we did (the ATM machine), and consists of a Python client program and a Java server. You may use this code as a starting point for this project, hwoever you do not have to. You may use any Thrift-compatible client and server language.

Computer accounts

You have setup two servers that you will use for this project. These servers are pre-configured with the Thrift compiler and necessary libraries. We will use these machines to test your project, and so your project must work on one of these machines. You do not have to develop your code on these machines, but you should test your project on them before turning it in.

Account information:

The hostnames of the servers you have access to for this project are:

You can run the client on one machine, and the server on another if you'd like, or run both the client and server on the same machine and connect to 'localhost'.

Your username is the same as the username associated with your email account, as defined by the course registration system. Your password is your secret number from gradesource.com. Just as a reminder, I've posted a Piazza post with a list of all the usernames so you can look up your account.

When you log into one of the above machines, it will ask you to reset your password away from the default to a new password--please select a good password.

Note that your home directory on vm135 and vm136 is not automatically backed up. So please keep your software in e.g., a private github.com account, or make periodic copies to another location you control.

Software

Both vm135 and vm136 have a relatively broad compliment of software languages (Java, C++, Python, etc).

The Thrift compiler is located at /usr/bin/thrift

The Thrift Java libraries are located at /usr/share/java/thrift-0.9.0.jar

Assignment

For this assignment, you will be implementing the client and the server of the Twitter service. You will need to ensure that the above-described Twitter semantics are faithfully executed by your implementation, which will depend on building a client (or clients) that can test all of the necessary corner cases.

0. Creating users (10%)

1. Managing the social network (15%)

2. Posting tweets (10%)

3. Favoriting/starring tweets (20%)

4. Reading tweets (45%)

Testing code

A few days after the project is handed out, we will provide you with a simple testing script that will test a subset of the required functionality. You can use this tool to verify your own testing code, and use it as the basis for your own, more comprehensive, test suite.

Example client-server interaction

Creating users:

[cse124aa@vm135:cse124] $ ./Twitter-remote -h localhost:9090 createUser alice
None
[cse124aa@vm135:cse124] $ ./Twitter-remote -h localhost:9090 createUser bob
None
[cse124aa@vm135:cse124] $ ./Twitter-remote -h localhost:9090 createUser oscar
None
[cse124aa@vm135:cse124] $ ./Twitter-remote -h localhost:9090 createUser bob
Traceback (most recent call last):
  File "./Twitter-remote", line 95, in <module>
    pp.pprint(client.createUser(args[0],))
  File "/home/cse124aa/Teaching/CSE124/wi15/cse124/Projects/proj3/solution/src/client/gen-py/cse124/Twitter.py", line 116, in createUser
    self.recv_createUser()
  File "/home/cse124aa/Teaching/CSE124/wi15/cse124/Projects/proj3/solution/src/client/gen-py/cse124/Twitter.py", line 137, in recv_createUser
    raise result.existsx
ttypes.AlreadyExistsException: AlreadyExistsException(user='bob')

Managing subscriptions:

[cse124aa@vm135:cse124] $ ./Twitter-remote -h localhost:9090 subscribe alice bob
None
[cse124aa@vm135:cse124] $ ./Twitter-remote -h localhost:9090 subscribe alice oscar
None

Posting tweets:

[cse124aa@vm135:cse124] $ ./Twitter-remote -h localhost:9090 post bob bob_tweet_1
None
[cse124aa@vm135:cse124] $ ./Twitter-remote -h localhost:9090 post bob bob_tweet_2
None
[cse124aa@vm135:cse124] $ ./Twitter-remote -h localhost:9090 post oscar oscar_tweet_1
None
[cse124aa@vm135:cse124] $ ./Twitter-remote -h localhost:9090 post bob bob_tweet_3
None
[cse124aa@vm135:cse124] $ ./Twitter-remote -h localhost:9090 post oscar oscar_tweet_2
None

Reading tweets:

By subscription:

[cse124aa@vm135:cse124] $ ./Twitter-remote -h localhost:9090 readTweetsBySubscription alice 100
[ Tweet(tweetString='oscar_tweet_2', numStars=0, posted=1424636804, handle='oscar', tweetId=-2652942106262094421),
  Tweet(tweetString='bob_tweet_3', numStars=0, posted=1424636801, handle='bob', tweetId=5761756974610927282),
  Tweet(tweetString='oscar_tweet_1', numStars=0, posted=1424636796, handle='oscar', tweetId=6275376381990199969),
  Tweet(tweetString='bob_tweet_2', numStars=0, posted=1424636786, handle='bob', tweetId=7295766383653288672),
  Tweet(tweetString='bob_tweet_1', numStars=0, posted=1424636779, handle='bob', tweetId=-99868626262713562)]

By subscription, limiting the number of responses:

[cse124aa@vm135:cse124] $ ./Twitter-remote -h localhost:9090 readTweetsBySubscription alice 2
[ Tweet(tweetString='oscar_tweet_2', numStars=0, posted=1424636804, handle='oscar', tweetId=-2652942106262094421),
  Tweet(tweetString='bob_tweet_3', numStars=0, posted=1424636801, handle='bob', tweetId=5761756974610927282)]

By user:

[cse124aa@vm135:cse124] $ ./Twitter-remote -h localhost:9090 readTweetsByUser bob 50
[ Tweet(tweetString='bob_tweet_3', numStars=0, posted=1424636801, handle='bob', tweetId=5761756974610927282),
  Tweet(tweetString='bob_tweet_2', numStars=0, posted=1424636786, handle='bob', tweetId=7295766383653288672),
  Tweet(tweetString='bob_tweet_1', numStars=0, posted=1424636779, handle='bob', tweetId=-99868626262713562)]

Deliverables

The turn-in is due Thursday, March 12th, 2015, 8:00am

Your turn-in should be a compressed tarball (.tar.gz) containing:

You should include a script called 'run-server.sh' in the src/server/ directory, that when run, starts up your server, listening on port 9090. You should also include a script called 'build-server.sh' that builds your code. Remember that your code should run on vm135.sysnet.ucsd.edu and vm136.sysnet.ucsd.edu.


Last updated: Mar 3, 2015.

Change log: