• Welcome to the new COTI server. We've moved the Citizens to a new server. Please let us know in the COTI Website issue forum if you find any problems.
  • We, the systems administration staff, apologize for this unexpected outage of the boards. We have resolved the root cause of the problem and there should be no further disruptions.

Trader: RESTful API

robject

SOC-14 10K
Admin Award
Marquis
With cargo working, I turn to my next project: a RESTful API.

Here's my working concept. Suggestions welcome.

Code:
[B]World actions:[/B]
GET /v0/map/spin/1910 ; data about Regina
GET /v0/map/spin/1910/jump/6 ; jump-6 list around Regina

[B]Player actions:[/B]
POST /v0/<playerID>/player ; create a new player
GET /v0/<playerID>/player ; player status

[B]Ship actions:[/B]
PUT    /v0/<playerID>/location?sector=<sector>&hex=<hex> [B]; jump to hex[/B]
PUT    /v0/<playerID>/fuelPurchase?type=refined&tons=<tons> [B]; buy refined fuel[/B]
GET    /v0/<playerID>/passengers
PUT    /v0/<playerID>/passengers/high [B]; load high passengers (etc)[/B]
DELETE /v0/<playerID>/passengers  [B]; unload all passengers[/B]
GET    /v0/<playerID>/cargo
PUT    /v0/<playerID>/cargo?tons=<tons>[B] ; buy speculative cargo[/B]
DELETE /v0/<playerID>/cargo?tons=<tons>[B]; sell/ditch[/B]
GET    /v0/<playerID>/freight
PUT    /v0/<playerID>/freight?tons=<tons>
DELETE /v0/<playerID>/freight

For the code itself, since I'm working in plain old Java, I'm writing a server (I'll thread pool it later) with an abstract factory that knows how to create the right API Version, which in turn has the appropriate Commands mapped to the actions described above (e.g. CREATE passengers, DELETE cargo, PUT ship). I'm thinking that mapping is just a switch statement router, although it could be a HashMap.
 
Last edited:
Code:
[B]Player actions:[/B]
CREATE /v1/player/<playerID> ; create a new player
GET /v1/player/<playerID> ; player status

[B]World actions:[/B]
GET /v1/map/spin/1910 ; data about Regina
GET /v1/map/spin/1910/jump/6 ; jump-6 list around Regina

[B]Ship actions:[/B]
[I]Player ID is in JSON body.[/I]
PUT /v1/ship/<sector>/<hex> ; jump to hex
PUT /v1/ship/fuel/refined/<tons>        ; buy e.g. refined fuel

[B]Passenger actions:[/B]
[I]Player ID is in JSON body.[/I]
CREATE /v1/passengers/high ; load ship with e.g. high passengers (etc)
PUT /v1/passengers ; get passenger counts
DELETE /v1/passengers ; unload all passengers

[B]Cargo actions:[/B]
[I]Player ID is in JSON body.[/I]
CREATE /v1/cargo/<tons> ; buy speculative cargo
PUT /v1/cargo/ ; get speculative cargo buy/sell price?
DELETE /v1/cargo/<tons> ; sell speculative cargo
(similarly for [B]freight[/B])

From a CRUD (Create, Read, Update, Delete) perspective, the HTTP verbs, respectively, are POST, GET, PUT, and DELETE.

As a general rule, PUT is used if you know the name of the resource you wish to create/update. GET retrieves the resource. DELETE removes the resource. POST is pretty much the "everything else", of which creation is just one aspect.

Consider the Player resource. If you know the name of the resource (i.e. /v1/player/Bob_Jones), you could create the player by simply using PUT to inject the resource. Take your player representation, PUT it to relate it to the name of the resource, and shove it it.

If you had, for example, /v1/randomplayer, you could make a POST request to this, and the result is a redirect to the new player it created (/v1/player/Joe_Random).

Clearly this is where you can perhaps consider something else as a player identifier (such as simply a synthetic ID (1, 2, 4, 4944, etc.). In that case, you would do something like take your player resource, and POST the representation to /v1/player, to wit the server would return with a redirect to the actual resource that it created: /v1/player/1234.

You could GET /v1/randomplayer to get a representation of a random character, but one that is not "created", its simply a resource of player. It wouldn't have an actual URL created for it. You could them POST it back to /v1/player, just like any other new player. So, you could GET, GET, GET until you find one you like, then POST it back to "save" it.

For the ship, a couple of things.

First, you probably would not want to make the ship location a first class resource, rather it's an aspect of the ship resource itself. So, in that case, it would be bundled within the rest of the stats on the ship. If you wanted to move the ship, you would change the location in the representation, and then PUT the whole thing back. It should be noted that the back end may not simply take that as rote, and try to enforce rules about the location change, but that's up to you. Similarly, the back end may make note of the ship location changing and do other things.

If you wanted to make the location a first class resource, then you could do something like:
Code:
PUT /v1/ship/<shipid>/location
Content-Type: application/json

{ sector: "spin", hex: 1901 }

Similarly with the refined fuel, if you just want to update the value.

If you want to actually "purchase" fuel, then you should create a purchase order, and POST it, and let it do the work:

Code:
POST /v1/purchase
Content-Type: application/json

{
    ship: "http://host.com/v1/ship/1234", 
    location: "http://host.com/v1/map/spin/1910",
    products: [
        {
            fuelType: "refined",
            amount: 65
        }
    ]
}
The purchase resource looks up the charge for the fuel at the location, checks the stock, deducts the balance from the ships balance, boosts the ships fuel value, etc.

The result would be something like /v1/purchase/1234, which would return the above, but include things like when it happened, and whether the purchase was approved (i.e. you had enough money, the planet had enough fuel, etc.).

You could do that in "your code", and simply PUT the ship back with a new balance, and a amount of fuel. But you can see the difference between a service that manages purchases (which could also be used to buy cargo), and simply CRUDing the ship resource.

For the code itself, since I'm working in plain old Java, I'm writing a server (I'll thread pool it later) with an abstract factory that knows how to create the right API Version, which in turn has the appropriate Commands mapped to the actions described above (e.g. CREATE passengers, DELETE cargo, PUT ship). I'm thinking that mapping is just a switch statement router, although it could be a HashMap.

You might want to look at something like DropWizard, which is a lightweight bundle JAX-RS and a server runtime, that way you could create your resources more declaratively and map your JSON to and from classes automagically

Code:
@Path("ship")
public class ShipResource {
    @POST
    @Path("/")
    @Consumes("application/json")
    @Produces("application/json")
    public Response createShip(Ship ship) {
        ....
    }

    @GET
    @Path("/{shipId}")
    @Consumes("application/json")
    @Produces("application/json")
    public Ship createShip(@PathParam("shipId") String shipId) {
        Ship ship = findShip(shipId);
        return ship;
    }
 
It looks good to me, and I have a couple of comments.

* This quote: "Player ID is in JSON body." makes me nervous. My memory of REST APIs, is that all data is passed in on the URL. So for this:
PUT /v1/ship/<sector>/<hex>
you would do either this:
PUT /v1/ship/<sector>/<hex>/<playerId>
or this:
PUT /v1/ship/<sector>/<hex>?playerId=<playerId>
I think the more you have in the URL, and the less you have in the body (for requests), the better. The result of the URL hit does go back in the body of the reply, but that is not true for requests. I think if everything is on the URL, it will be much easier to test, troubleshoot, understand, etc.

* As for whartung's suggestion that you have stuff like this:
ship: "http://host.com/v1/ship/1234",
location: "http://host.com/v1/map/spin/1910",
I'm not sure about that. I'm thinking about just doing stuff like this:
ship: 1234,
location: spin-1910
I'm not sure what you get by putting your own machine name and v1 in there. And especially if this data is going into the server, then I don't think it should be json'ed in the body of the request, but put as part of the URL. Like this:
POST /v1/purchase/<ship>/<product>/<amount>
That assumes the server knows where the ship is. If not, then:
POST /v1/purchase/<ship>/<location>/<product>/<amount>

* I don't know what is the difference between cargo and freight.

* I know a lot of people like to use the "Play" framework for making Java based REST servers. The code that I've seen from "Play" looks a lot like the DropWizard example code below, so it probably is similar.

Joshua
 
my current project for a ship object is as below. I've started messing with 'load the related tables when loading a ship' thing as per that last line. So if your API returned a ship object it may have all the gritty details filled out.

So you could get the ship.theClass.Dtons for the tonnage. This is c#, but both Java & C# derive from C and are very similar (except where they are not :) )

Code:
public class Ship
    {
        // primary key
        public int ShipId { get; set; }

        public string Name { get; set; }
        public int CargoCarried { get; set; }
        public int Credits { get; set; }

        public int ShipClassID { get; set; }
        public int WorldID { get; set; }
        public int SectorID { get; set; }

        public string Era { get; set; }

        public int Day { get; set; }
        public int Year { get; set; }

        [NotMapped]
        public ShipClass theclass => TravellerTracker.App.DB.ShipClasses.Where(x => x.ShipClassID == this.ShipClassID).FirstOrDefault();
    }
 
That's just a web/servlet server. DropWizard is a JAX-RS server (among other things) that readily support fat jar deployments, Play is a (full stack I think) application framework.

Undertow is light weight because it doesn't do anything.

The JRE already comes with an in built HTTP (not Servlet) server: https://docs.oracle.com/javase/7/do...c/com/sun/net/httpserver/package-summary.html

If that's all you're interested in. (Yes it's a com.sun.* package, no it's not going anywhere.)

Development model is a little creaky, but what do you want for nothing.

I'd still suggest JAX-RS. DropWizard is basically painless JAX-RS if you're looking for a fat jar program you can just run with "java -jar YourApp.jar" vs dealing with a container.
 
I'd still suggest JAX-RS. DropWizard is basically painless JAX-RS if you're looking for a fat jar program you can just run with "java -jar YourApp.jar" vs dealing with a container.

When I surfed over to DropWizard, its landing page suggested the preferred build system for using it is Maven. But can't I simply grab the DropWizard JAR (assuming there is one) and plop it into my IntelliJ?
 
Whartung and Joshua, thank you for your suggestions so far. You're helping me think about my model in various ways. As a result, I've changed the version to Version 0 (zero) to reflect its plan-to-throw-one-away style.

Due to Whartung's suggestions, I've demoted certain values down from first-class status. Then from Joshua's suggestions, I've made them URL parameters. This reduces the body requirements, which I may reverse in Version 1. Or not. At any rate, Postman is there when I need it.
 
When I surfed over to DropWizard, its landing page suggested the preferred build system for using it is Maven. But can't I simply grab the DropWizard JAR (assuming there is one) and plop it into my IntelliJ?

As for Dropwizard, no. A "hello world" program using DropWizard require 86(!!) separate jars to run.

Welcome to Maven.

I can't speak to IntelliJ, I've never used it. But you really should look in to Maven.

Consider, to get started with Jersey, the JAX-RS reference implementation (just Jersey, DropWizard is more than just Jersey), using Maven, you need to declare two dependencies:
Code:
        <dependency>
            <groupId>org.glassfish.jersey.containers</groupId>
            <artifactId>jersey-container-grizzly2-http</artifactId>
        </dependency>
         <dependency>
            <groupId>org.glassfish.jersey.media</groupId>
            <artifactId>jersey-media-moxy</artifactId>
        </dependency>

Ostensibly, each dependency represents a single jar. But more than that, each dependency represents not just the jar itself, but the other jars it depends on. That chain is walked until the entire required list of jars is obtained that are necessary to run the application.

DropWizard ostensibly has a single dependency. But pulling on that string, and you download half the internet.

Based on the two dependencies represented above, the actual list of jars required when the application is run is:

Code:
jersey-container-grizzly2-http-2.17.jar
javax.inject-2.4.0-b10.jar
grizzly-http-server-2.3.16.jar
grizzly-http-2.3.16.jar
grizzly-framework-2.3.16.jar
jersey-common-2.17.jar
javax.annotation-api-1.2.jar
jersey-guava-2.17.jar
hk2-api-2.4.0-b10.jar
hk2-utils-2.4.0-b10.jar
aopalliance-repackaged-2.4.0-b10.jar
hk2-locator-2.4.0-b10.jar
javassist-3.18.1-GA.jar
osgi-resource-locator-1.0.1.jar
jersey-server-2.17.jar
jersey-client-2.17.jar
jersey-media-jaxb-2.17.jar
validation-api-1.1.0.Final.jar
javax.ws.rs-api-2.0.1.jar
jersey-media-moxy-2.17.jar
jersey-entity-filtering-2.17.jar
org.eclipse.persistence.moxy-2.5.0.jar
org.eclipse.persistence.core-2.5.0.jar
org.eclipse.persistence.asm-2.5.0.jar
org.eclipse.persistence.antlr-2.5.0.jar
hamcrest-core-1.1.jar

Maven not only manages that list for you, it will also download the jars during the build and store them locally. In the old days, you would need to hunt down and download the jars and add them to your project yourself, but with Maven, you don't. There's an entire infrastructure out there to help manage this for you (for good and ill).

Maven projects have become the ubiquitous project format for Java today. It ubiquitous because all of the IDEs have reasonable support for it. Netbeans works with it natively, Eclipse can import Maven projects, I don't know what IntelliJ can do (I don't know if the free version has any support at all). We have people using all of these IDEs here at work on our shared Java code.

So, if you want to share your code in the Java world, the friendly way to do it is to publish a Maven project -- that way everyone can use their IDEs easily. If you want your libraries used in the Java world, it's even more friendly to publish the binary jars to the Maven central repository. Then when someone wants to use your library, they simply add it as a dependency, and like a thread on a sweater, they get not just your code, but anything you rely on as well to support it (like, say, a logging framework, or Json processing).

There are also other tools out there that leverage the Maven infrastructure. Gradle is a new(er) Groovy based build tool that takes advantage of the Maven central repository. Ivy adds Maven dependency support to Ant.

I get it, I'm not a huge fan of Maven and its declarative style. I also wish it were faster. It's more of a "here's what I want" and figures out what to do than a "go and do this" style of system, and it can have a big learning curve. However, there's "cut and paste" friendly stuff all over the interwebs that let you treat is as a mostly black box for simple projects, and it makes working with Java on the Internet MUCH, MUCH easier.

I don't understand it, I live with it and we get along reasonably well.

If you get maven installed, and then type:
Code:
mvn archetype:generate -DarchetypeArtifactId=jersey-quickstart-grizzly2 -DarchetypeGroupId=org.glassfish.jersey.archetypes -DinteractiveMode=false -DgroupId=com.example.rest -DartifactId=jersey-service -Dpackage=com.example.rest -DarchetypeVersion=2.17

It will create a Maven project with a "hello world" JAX-RS example that runs from the command line. This reaches out to the central repository, pulls down an Archetype (identified by the Artifact ID and Group Id above), and the archetype is a project template.

You then can type:
Code:
mvn clean compile exec:java

That cleans, downloads (and caches) all of the dependencies, builds, and executes the server process. (Those are three separate command for maven.)

The nice thing about the exec:java command is that it builds a class path up based on your local repository. You can also build a "fat jar" that bundles everything in to one, large jar that you can run with "java -jar HelloWorld.jar". But that takes more time. Better to compile you 500 lines of code and be done with it than build up a 10M jar every time.

You should be able to import that project in to your IDE somehow, and then add source code, rinse and repeat.

To be honest, though, I don't do much with maven beyond building projects. All my server stuff is deployed to Glassfish or Tomcat. But I've made fat jars, etc.

JAX-RS is really nice. It's spec is less than 80 pages. And it's very good at making and writing HTTP based services. It handles routing, payload marshaling, and content negotiation.

But as you can see, it's not a small piece of software.
 
Yeeeahhhh.

OK. We used grails (yeah yeah) at work, so I understand the MegaBLOK way of build-system-rails-like-framework-and-oh-yeah-here's-the-API-look-ma-its-all-magic approach. And you know what, that's probably what I will end up needing.

Today, though, I will get by with Java's builtin itty bitty web server and register handlers programmatically. Because a fancy server is not a priority currently, it's a distraction.

Heck, I don't have a place to run this server. I run it on my home OSX machine. Maybe I'll try to shoehorn it into my Raspberry Pi.
 
As for Dropwizard, no. A "hello world" program using DropWizard require 86(!!) separate jars to run.

Welcome to Maven.

I'm going to suggest that Maven is not the tool you want, but should be using Gradle instead. The primary reason is writing maven scripts in XML is a huge pain. The gradle DSL (make file) is much smaller and concise.

I use intelliJ every day as part of my Job. It is a really nice IDE with integration with both Maven and Gradle, a really good debugger, and the ability to run both a client and server at the same time.
 
Yup. Our Grails system uses Gradle. I'd rather do that than Maven as a result. And I think when I eventually replace my works-for-now tiny http server, it'll probably be the grails/groovy/gradle mix.
 
I set my shipbuilder app up on Heroku with the intention of hosting it on my own server later.

Later hasn't happened because it took a year before it was used enough to incur any Heroku costs and that was because the DB went over what was available for free.

The only downside is it take 20 seconds to spin up the app when you first hit it. The big upside is that you can focus on hacking out code and deal with server issues later, if you ever need to (or you just plain want to :)).

Heroku can cope with most current languages and you just set up a config file to tell Heroku what you are running. If you are used to using GitHub or GitLab, you can push code to Heroku the same way.

Heroku with a basic sized DB costs me US$9 a month. I'm sure I could find a cheaper option, maybe running Apache on an old PC, but at the moment I'm not phased and I'll probably sort it when I get time to expand the app again.
 
Small update

I've added the nearly-most primitive persistence possible.

It serializes the Player object to disk.

THANK GOODNESS I don't have to deal with the Map. That's one less thing to think about. Thank you Joshua.

Now I can kedge up the first API call, a kludgey one that gets things moving: one that jumps to a destination, unloads passengers and freight, takes on passengers and freight, gets a list of your destination options, and returns all that in JSON. Something messy like:

Code:
PUT /v0/<playerID>/ship/jump-drive/<sector>/<hex>
 
Updated my Trader code a bit. It's here:

https://github.com/bobbyjim/Trader

Simple HTTP server. Because it doesn't matter.
Simple JSON and HTTP parameter parsing. Because it doesn't matter.
Neanderthal REST processing, because it's very new.

Feel free to steal the code and set up a server.

There is currently one command-line client, and two very basic REST calls. Both interact with the same serialized player objects, so you could "administratively" manage an on-line player's data.

The REST calls are, as I said, Neanderthal. They are:

Code:
GET /v0/ship/<playerid>
GET /v0/ship/<playerid>/<destination selection #>

The first call returns the player's status, and a numbered list of destinations in range of the ship. Also creates the player, if he doesn't exist, but we won't talk about that because it's not a POST (all new players start on Regina with an unarmed Type A2).

The second returns the player's status and sets the player's jump destination, which will happen the next time the player's status is fetched.

So it's a very simple pair of calls for moving one's ship around Charted Space. Since the map is Travellermap, the limits are Travellermap's limits.

The response body isn't JSON yet. I told you it's Neanderthal. Only take a moment, when I have a moment.
 
Back
Top