NetKernel/News/3/39/September_28th_2012
search:

NetKernel News Volume 3 Issue 39

September 29th 2012

What's new this week?

Catch up on last week's news here

Repository Updates

The following updates are available in the NKEE and NKSE repositories...

  • kernel 1.30.1
    • Changed state transition so that hit of resolution cache now uses resolved scope to try for hit in representation cache further increasing opportunity for cache hit.
  • layer0 1.92.1
    • Fixed modules.d hashcode check so that first boot doesn't cause an unnecessary false synch.

The following update is available in the NKEE repository...

  • cache-ee 1.3.1
    • added constraint of 8 to number of cache keys that can be attached to one item to prevent growth of unnecessary unresolved scope keys.

Durable Scope Blog - Dynamic Grammars

This week Tony describes the many ways in which you can enable dynamically generated grammars and shows an implementation using NKF's ConfiguredEndpoint base class. Everything is a resource - even grammars...

http://durablescope.blogspot.co.uk/2012/09/dynamic-grammars.html

Resource Oriented Analysis and Design - Part 6

Sixth in a series of articles taking a well understood domain problem and casting it as an ROC solution. Read Part 1 Here, Read Part 2 Here, Read Part 3 Here, Part 4 Here, Part 5 Here

Recap

Last week we discussed how with ROC its a useful working practice to work from both edges towards the middle. We built a web rendering layer and added the ability to make a move. By delegating state management to the edges (client-side AJAX) we implemented a first rough cut of the game.

This week we're going to add a little finesse and in the process look at stateless business logic...

Preventing replay of taken cells

Last time, we left the game in a playable condition but noted that it was violating one of the basic rules of TicTacToe in that it was possible to overwrite the state of a cell by playing another move on it. So now we need to fix this.

All that we need to do is change the implementation of the res:/demo/tictactoe/move endpoint so that before SINKing the move's token state to the cell, it first SOURCEs the cell and checks that it is empty (ie the default platonic empty string "" cell state)...

cell=context.source("httpRequest:/param/cell")
state=context.source(cell)
result=null
if(state.equals(""))
{   state=context.source("httpRequest:/param/state")
    context.sink(cell,state)
    result="OK"
}
else
{   result="TAKEN"
}
resp=context.createResponseFrom(result)
resp.setExpiry(resp.EXPIRY_ALWAYS)

The resulting representation is now either "OK" or "TAKEN". In the HTML rendering layer, you'll recall that the AJAX script only changes the client side state if "OK" is received. So a response of "TAKEN" is ignored and therefore makes a repeat move on a cell impossible.

Do we have three in a row? The CheckSet Meta Resource

To be a slightly more sophisticated solution it would be nice if we can determine if someone has won. You'll recall that back in Part 1 we anticipated this requirement in our modelling and discussed Meta Resources. In particular I proposed the idea of the "CheckSet" resource.

The idea for this resource is that for any given cell, there must be a corresponding row, column and potentially diagonal(s) that we need to look at to decide if the game has been won.

So, say we play cell c:0:0, then we have to check the cells in row:0, column:0 and diagonal:0 to see if any of these sets is all equal. If one is, then the game has been won.

With a code-based perspective you might start by modifying the implementation of the move service to contain the logic to source all the necessary testable cells... but that would make for a very complicated lump of spaghetti business logic. Not something you'd want to write or maintain.

The ROC way is to think of the CheckSet as a resource. There is one CheckSet for every cell on the board. You'll see what I mean if I throw you in to the deep end of my implementation. In 30 seconds I added the following endpoint in the wwwMapperConfig.xml...

<endpoint>
  <grammar>
    <active>
      <identifier>active:checkSetidentifier>
      <argument name="cell" />
    active>
  grammar>
  <request>
    <identifier>active:groovyidentifier>
    <argument name="operator">
      <literal type="string"> import org.netkernel.layer0.representation.impl.* cell=context.getThisRequest().getArgumentValue("cell") s=cell.split(":") x=Integer.parseInt(s[1]) y=Integer.parseInt(s[2]) b=new HDSBuilder() b.pushNode("test") b.addNode("id", "row:"+y) b.addNode("id", "column:"+x) if(x==y) { b.addNode("id", "diagonal:0") } if(y==(-1*x)+2) { b.addNode("id", "diagonal:1") } resp=context.createResponseFrom(b.getRoot()) literal>
    argument>
    <argument name="cell">arg:cellargument>
  request>
endpoint>

Briefly, I've used an active grammar that expects an argument named "cell". This will be the "c:x:y" cell identity for which we require the CheckSet.

In the first 4 lines of code I extract the identity (value) of the cell argument from the request. I know the structure so I split the c:x:y string up and parse the x and y integer values. To recall the methodology of last week: Step 1 is complete. I know what I've been asked for.

There is no step 2, since I don't need any other resource to satisfy this request. In ROC terms we can think of the CheckSet as being a Platonic Resource - it is an abstract entity like a "Platonic Form", it has always been there and it will always be there (no really).

Step 3 is the value add part. You can see that this demands that I get off my philosophical high-horse and get down and dirty with a concrete representation. I don't waste any time worrying about the physical form of the representation, as I have explained previously, a good rule of thumb is: when you don't know or don't care then use a tree-structure.

Here, using an HDSBuilder, I construct an HDS tree and push a root node of "test". Next I add an "id" node with a value of "row:y" (the identity of the y row) and also the same for "column:x" since every cell is always a member of one row and one column.

Dealing with the diagonals requires a little consideration of linear equations. We understand that the diagonals are lines and therefore must follow the function y=mx+c. In our naming convention defined in part 1 we agreed that diagonal:0 goes from top-left to bottom-right so this is the line y=x. Equally diagonal:1 goes from bottom-left to top-right. With a moments thought its apparent this is the line y=2-x.

Now, we only need to consider a diagonal if our cell is actually on either of the two diagonal lines. So now you see what the two conditions in my code are doing to take care of this.

To see what this endpoint provides, here are three examples of the CheckSet corresponding to cells top-left (c:0:0), top-center (c:1:0) and center-center (c:1:1). As we have repeatedly seen in earlier parts, these follow the pattern of being linked data resources containing references to the identities of other resources...

Cell CheckSet
c:0:0

<test>
  <id>row:0id>
  <id>column:0id>
  <id>diagonal:0id>
test>

c:1:0

<test>
  <id>row:0id>
  <id>column:1id>
test>

c:1:1

<test>
  <id>row:1id>
  <id>column:1id>
  <id>diagonal:0id>
  <id>diagonal:1id>
test>

Lastly, returning to the implementation code, notice that I have not expired the response. In our constrained game board there are only 9-possible CheckSets. When we have generated one we can keep it forever. It follows that we should expect this code to run precisely nine times in the entire life of this system.

Now we need to see how to use a CheckSet...

Linked Data Logic

For each move made, the CheckSet tells us which resources need to be looked at to decide if the game has been won. So now I can enhance my move service...

cell=context.source("httpRequest:/param/cell")
state=context.source(cell)
result=null
if(state.equals(""))
{   state=context.source("httpRequest:/param/state")
    context.sink(cell,state)
    req=context.createRequest("active:checkSet")
    req.addArgument("cell", cell)
    rep=context.issueRequest(req)
    ids=rep.getValues("/test/id")
    for(id in ids)
    {   //Need to check the cells are equal
        println(id)
    }
    result="OK"
}
else
{   result="TAKEN"
}
resp=context.createResponseFrom(result)
resp.setExpiry(resp.EXPIRY_ALWAYS)

After we SINK the cell state, we request active:checkSet for the cell.

The CheckSet's HDS tree structure allows me to select the set of id nodes from the tree with an XPath "/test/id". So now I know the identity of every resource I need to look at to see if the game is over. For the moment I just iterate over them spitting out the identity of each resource.

So now we need to know when a composite resource (a set of cells) is all equal...

Set Membership Equality

I copy and paste my active:checkSet endpoint declaration. I delete the code and modify the grammar to active:testCellEquality and specify an argument name of "id", the identity of the composite resource set of cells to test for equality.

Here's the implementation...

<endpoint>
  <grammar>
    <active>
      <identifier>active:testCellEqualityidentifier>
      <argument name="id" />
    active>
  grammar>
  <request>
    <identifier>active:groovyidentifier>
    <argument name="operator">
      <literal type="string"> rep=context.source("arg:id") cells=rep.getValues("/cells/cell") result=true; last=cells[0]; for(cell in cells) { if(cell.equals(last)) { last=cell; } else { result=false; break; } } resp=context.createResponseFrom(result) literal>
    argument>
    <argument name="id">arg:idargument>
  request>
endpoint>

Using our ROC methodology again, we can analyse this endpoint.

  • Step 1 (what am I promising) is explicitly declared in the grammar. I have promised that I will provide the resource that says if another resource's cells are all equal.
  • Step 2 (what do I need to do this). I obviously I need the resource in question. So I must source the resource specified in the "id" argument. Notice that NKF provides a convenient convention to do this. I can SOURCE "arg:id" since the "arg:" prefix says SOURCE the resource who's identifier is the value of the argument. If the id argument is "row:0" this will SOURCE row:0. Using "arg:" is like dereferencing a pointer.
  • Step 3 (add value). We know that all composite resources in the TicTacToe resource model are implemented using HDS and have the tree structure /cells/cell. So using an HDS XPath we can get the values of all the cells. The logic looks at the cells, and allowing for fast fail on a difference, tests to see if all the cells have the same value.
  • Step 4 (keep your promise). We return a boolean true if the cells are all equal.

Notice this code has no error handling. Why? Because if you pass it a resource reference that does not reify to an HDS representation with the correct structure it will throw an exception. We're saying "You're not keeping your side of the bargain and we're not the right resource for you to request - so deal with it buddy". But from the requesting side we also know that the resource identifiers that we will pass here are only ever going to be those in the CheckSet and we know from our initial construction of the TicTacToe resource model that those identifiers always reify a suitable representation.

We're now ready to finish the solution...

Final Step

So now, back in the move implementation, we can check each item in the CheckSet...

cell=context.source("httpRequest:/param/cell")
state=context.source(cell)
result=null
if(state.equals(""))
{   state=context.source("httpRequest:/param/state")
    context.sink(cell,state)
    req=context.createRequest("active:checkSet")
    req.addArgument("cell", cell)
    rep=context.issueRequest(req)
    ids=rep.getValues("/test/id")
    result="OK"
    for(id in ids)
    {   req=context.createRequest("active:testCellEquality")
        req.addArgument("id", id)
        if(context.issueRequest(req))
        {   result="GAMEOVER"
            break;
        }
    }
}
else
{   result="TAKEN"
}
resp=context.createResponseFrom(result)
resp.setExpiry(resp.EXPIRY_ALWAYS)

For each id we now request active:testCellEquality for that resource. (Remember the CheckSet table above, for cell c:0:0 we request tests for row:0, column:0 and diagonal:0). As soon as we find that a resource contains all equal cell state then the game is over. The current move is the winning move and we have a filled row, column or diagonal.

Finally, back in the HTML rendering layer, we hack the AJAX code to deal with the new possibility of a "GAMEOVER" response...

function bindCell()
        {
            //alert("Binding");
            $(".cell").click(
                    function()
                    {   if(player!="GAMEOVER")
                        {
                            var cell=$(this);
                            $.get("move?cell="+cell.attr("id")+"&state="+player, function(data)
                                {   if(data=="OK")
                                    {   togglePlayer();
                                        refreshBoard();
                                    }
                                    if(data=="GAMEOVER")
                                    {   refreshBoard();
                                        alert("We have a winner! \n"+player+" wins")
                                        player=data;
                                    }
                                }
                                , "text"
                            );
                        }
                    }
            );
        }

The game now plays for real and terminates naturally if a winner is found or the cells are all full.

Notes on working practice

This week I mostly refreshed and clicked cell c:0:0 over and over. But AJAX is a pain to develop if you don't know a powerful trick. For the entire development and evolution of the move service I kept the visualizer on. After each change I would clear the visualizer state, click cell c:0:0 and then dive in and look at the ROC domain requests.

I would do stupid things like have typos in my groovy implementation of CheckSet etc etc. The visualizer shows these exceptions and I could inspect them retrospectively at my leisure (by clicking "show response" on the red exception state).

By simple iterative refinement over the course of about 15 minutes I evolved my solution until it worked.

Next time we'll apply some final polish and add some constraints. In the mean time why not try the demo and use the visualizer to look at the requests and the cache hits. You'll find that our resource oriented solution is minimising computational operations and has normalized the solution...

Checkpoint

You can download a snapshot of the latest modules at this point here...

Note: You will need to accept last week's update to pds-core from apposite if you don't already have it.

[Read part 7]


Have a great weekend.

Comments

Please feel free to comment on the NetKernel Forum

Follow on Twitter:

@pjr1060 for day-to-day NK/ROC updates
@netkernel for announcements
@tab1060 for the hard-core stuff

To subscribe for news and alerts

Join the NetKernel Portal to get news, announcements and extra features.

NetKernel will ROC your world

Download now
NetKernel, ROC, Resource Oriented Computing are registered trademarks of 1060 Research


WiNK
© 2008-2011, 1060 Research Limited