Iteration 0: Understanding the SkyCave Architecture. Broker

Learning objectives

You should understand the core architeture of SkyCave, have a development environment set up, and be able to add features to it that are validated though automated tests.

Deadline

September 28th at 23.59. That is, after this time, none of the exercises in this iteration (on this webpage) that are marked M can be handed in on BrightSpace and thus cannot be awarded any points.

Exercise 'groups'

Form groups of two or three persons and register yourself in BrightSpace to get a groupName from those defined in BrightSpace: Alfa, Bravo, etc.

Exercise 'cave-execution'

Gradle is used to execute the server ("daemon") and the client ("cmd") of SkyCave.

Read the README.md file in the root of the SkyCave folder for an explanation of how to start a server on localhost and next start a client to connect with it.

Play around with the command set of the 'cmd' - you can get help on the command set from the 'h' command.

Exercise 'cave-gradle-structure'

SkyCave uses Gradle as build management tool for compiling, testing, execution, packaging, and much more. It is structured as a multi-project that separates SkyCave into three separate modules (which is called 'subprojects' in Gradle terminology): common, server, and client, reflecting a classic tiered architecture. You will later add more subprojects as you refactor the architecture into a microservice architecture, add integration tests, etc.

Exercise 'cave-intellij'

In the following exercises and mandatory project you will inspect code and write code. IntelliJ is installed in the course VM but you are free to use any other IDE if you prefer.

If you do not know IntelliJ it is as any other modern IDE: It is a swiss army knife that can do anything but the learning curve is steep, getting to know a zillion short-cut keys :(.

Go find a PDF on the www with the default keymap, but be sure to know

Exercise 'cave-broker-architecture'

Consider the scenario:

"Mikkel logs into SkyCave with his credentials. He LOOKs around, and then moves NORTH. Here, he DIGs a new room in direction EAST with the description "You have entered a dark cave." Next, he moves EAST and LOOK to see the description of the new room. He then QUITs the cave.

Execute the scenario using the fully stubbed Cmd target (gradle cmd -Pcpf=local.cpf = run 'cmd' using configuration defined in 'local.cpf')

Your task in this exercise is to understand the flow of data and control from the client to the server and back as defined by the Broker pattern. As our Broker is configurable and as some of the code is default code from the FRDS.Broker library, we will take it in a few steps.

  1. Locate 'TestPlayerServant' in the test tree of the server subproject, and review some of the test cases. These are the test cases that validate that the server side/domain implementation of the Player role is properly working.
  2. Now, locate 'TestPlayerProxy' in the test tree of the client subproject. It basically reuses the same set of test cases, but tests that the full Broker chain correctly transfers control from the client side and back again. Refer to the FRDS book as you go along. The tricky part is actually setting up the implementations of the relevant Broker roles, notably the 'Invoker' and the 'ClientProxy'ies. Review the 'createCaveProxyForTesting()' method.
  3. The test cases uses a stubbed Broker network layer implementation: 'LocalMethodCallClientRequestHandler'. If you find the code for it, you can enable the two print statements in the 'sendToServer()' method and execute one of the 'TestPlayerProxy' test cases to see the request and reply objects being transmitted over the "network"."
  4. Ok, now try to trace the execution and sketch an informal sequence diagram with emphasis on the client-server communication for the outlined Mikkel scenario, which goes from the PlayerProxy to the PlayerServant implementations using the Broker pattern.
  5. Review the Broker abstraction that are not provided by the FRDS.Broker library, the 'Invoker', which is responsible for the server side upcall to the Servant implementations: and the 'ClientProxy' which translates client side method calls into requests objects to be handled by the Requestor.
  6. To have a testable architecture, the SkyCave code base uses compositional design and dependency injection, so the core roles can be told to collaborate with test doubles. We will return to test doubles in more detail in a few weeks, but for now, have a look at how (especially) the server side code uses in-memory/fake implementations for the CaveStorage, SubscriptionService, QuoteService, etc.

Next, spin up an application server ('daemon') on localhost ('gradle daemon -Pcpf=http.cpf') and perform the same scenario using the Cmd target configured for localhost communication ('gradle cmd -Pcpf=http.cpf').

  1. Trace the execution of the scenario in this configuration. (It is not much different, except the main program CaveDaemon is now used.)

Finally, run separate Cmd's for Mikkel, Mathilde, and Magnus, each in their own shell. ('gradle cmd -Pcpf=http.cpf -Pid=mathilde_aarskort -Ppwd=333', you will find the known users in the TestStubSubscriptionService.java source code as well as in the README.md).

  1. Verify that the players/avatars can move to the same places and 'see' each other, using the LOOK command.
  2. Log Mikkel in twice in two seperate shells, and explain what happens in the two clients. Locate the code in the code that defines this behaviour.
  3. While having several clients logged in, stop/kill the server, and review the result in the clients.

Review the JavaDocs of the code and refresh you memory of the used patterns.

Exercise 'cave-testing'

First, run the full test suite using gradle test from the command line.

Next, run the test cases in IntelliJ. You can do this one module at a time by right clicking the module root (or the src/test/ folder within) and select 'Run All Tests'. Or, better, you can make a configuration for running all tests by

  1. Ctrl-Click the modules you want ('server' and 'client')
  2. Right-click one of them and 'Run All Tests'.
  3. This will create a Test Configuration named 'Whole project' which appears in right side of the the menu bar. From now on you may select that from the drop down and just hit the green 'run arrow' right of it to rerun all tests.

Review the test cases used to develop and test SkyCave. Start in the server side tests of Cave and Player; next review the similar tests on the client side.

Next, try to 'Run All Tests with Coverage', first with the server, and next with the client. Ensure to select 'Add to active suits' for the latter run - it will accumulate the test coverage for both the server and client tests. Now a package browser appears and you can browse your code and see coverage statistics, and 'red/green' coloring of the Java production code.

Be puzzled that the teacher has not achieved 100% code coverage, inspect the weak spots, and explain why the teacher has not bothered.

Try to run coverage from Gradle instead using gradle test jacocoRootReport. You have to browse to the 'build/reports/jacoco/jacocoRootReport/html/index.html' to see statistics and browseable code.

Please appreciate the weird 'jacocoRootReport' target in the root build.gradle file. It has really cost me a lot of hours! If any gradle nerd out there spots anything that could be made more elegantly, I would appreciate it highly!

Exercise 'logging'

Review 'log4j.properties' in the respective client and server subprojects' resource folder. Find out what the configuration of logging is. Wondering why logging is going to the console and not to a logfile for the server? Docker prefers it that way, which we will return to in a few weeks...

Suggest improvements to the logging (which admittedly is not quite at production quality at the moment!)

Exercise 'configuration'

Over the course you will configure the SkyCave daemon and cmd specifically for each and every exercise. For this to work, a strong dependency injection and configuration architecture is necessary which can instantiate and dependency inject the proper delegates. This is done though reading Cascading Property Files or 'CPF's, which is my own invention (and have been invented by several others before me :).

Revisit the FRS chapter on Abstract Factory and skim the paper on Object Manager. These two patterns form the core of SkyCave's dependency injection architecture.

Trace how the ObjectManager in the 'daemon' is populated with the proper delegates during execution of 'gradle daemon -Pcpf=http.cpf'.

An important feature of the used CPF library is the chaining feature, which allows one configuration file to inherit/chain all definitions from an ancestor CPF file.

  1. Review the server's 'socket.cpf' CPF file which defines all configurable properties for the daemon. It is in the standard Gradle resource folder: '/src/main/resources/cpf/socket.cpf'. Note that most dependency injections require two properties: Note: Most services are at this point just stubs so no connection is made to external services. You will soon change that.
  2. Next, review the server's 'http.cpf' CPF file and note that most properties are chained from the 'socket.cpf' file.
  3. Review the 'load.cpf' configuration. Try to run the server with that instead of 'http.cpf' and then on purpose log in an unknown user with wrong credentials. Explain what happens.
  4. Create a 'dummy.cpf' which chains 'cpf/http.cpf' and next sets the property
                  SKYCAVE_QUOTESERVICE_SERVER_ADDRESS = www.theserver.dk:9999
                
    Run a daemon using this CPF, and contact it with a cmd. Use the 'sys' command to verify that the configuration is indeed set to 'www.theserver:9999' of the quote server.

Exercise 'scm'

Decide on a Git hosting provider and create a non-public repository for your group to share code and material.

Choosing BitBucket is a bit favorable, as my lessons will use bitbucket pipelines when we come to the Continuous Delivery topic.

The code and configurations you make for SkyCave is the exam and thus you are not allowed to share excessive code fragments nor entire codebases across teams. Make sure your repo is not public!

Spot checks will be made and violation of this rule is deemed very problematic. Downloading/copying from accidentially public repositories with SkyCave material is considered exam cheating.

Exercise 'quote-client' (*) [M+A 30+10]

Learning objective: You will review the abstractions/patterns for distributed method calls (Broker) between client and server; and use these to implement the missing bits to make the 'getQuote' method work from the client side.

The quote service related methods are not implemented on the server side (in PlayerServant), but instead a hard-coded test value is returned, which suffices for now to develop the broker interaction.

Implement test cases (in client project's 'TestQuoteClient') and proper client side proxy (PlayerProxy) and method dispatching (PlayerInvoker) so clients can retrieve the (hard-coded) quote from the server. Ensure high test coverage.

Hint: Seach for (some of) the TODO tags in the code base. In IntelliJ, you can use 'View->Tool Windows->TODO'.

Requirements:

  1. You must use the following fixed method key (look in the MarshalingKeys.java file):
      public static final String GET_QUOTE_METHOD_KEY = PLAYER_TYPE_PREFIX + "get-quote";
    

Hand-in:

  1. Submit your source code files for 'PlayerProxy', 'PlayerInvoker', and 'TestQuoteClient' in BrightSpace. (30 Points).
  2. Once you have your solution code in a Docker image (solved 'skycave-image' in a later iteration), submit it to Crunch, for functional verification. (10 Points).

Evaluation:

Your submission is evaluated against the learning goals and adherence to the submission guidelines. The grading is explained in Grading Guidelines. I will use the grade sheet to evaluate your submission. The score counts towards your final grade.

The functionality will be tested by Crunch, later on.

Learning Goal Assessment parameters
Submission Required artifacts (source files) are all present.
ClientProxy Code Client side proxy code obeys the Broker pattern. The proper MarshalingKeys constant is used. Robert Martin 'Clean Code' properties are generally kept.
Invoker Code PlayerServant Invoker code obeys the Broker pattern. The proper MarshalingKeys constant is used. Robert Martin 'Clean Code' properties are generally kept.
Test Code The test code is simple (no complex constructs, basically only assignments, simple private method calls, very basic for loop). The test code reflects using the TDD principles (Isolated Test, Evident Test, Evident Data, etc.). The production code is 'well' covered by test code (JaCoCo coverage is 'green' for most production code).

Exercise 'wall-client' (*) [M+A 30+40]

Learning objective: Again, use the Broker pattern to handle client/server communication, this time transferring lists and complex objects over the wire.

The (very limited) 'social network' feature of Cave is the POST and READ commands in the Cmd, which allows a player to write messages on the wall of a room, and read all the messages posted. (Note: we specify that a room has only a single 'wall' = a whiteboard for posting messages.)

If a player 'Mikkel' POSTs 'Hello Cave!', and 'Mathilde' one minute later POSTs 'Great place.' then Mathilde (and Mikkel or anyone else) should read

0: [Mathilde, just now] Great place.
1: [Mikkel, 1 minutes ago] Hello Cave!
      

if she READs the wall. Postings to the wall are sorted, newest first. That is, they get older the longer they are down the list. The author of a message can overwrite it by the UPD command by referring to the index of the message (the number before the message.)

Wall messages are paginated, with page 0 being the page containing the newest/latest messages. (Player.WALL_PAGE_SIZE pages.)

Server side code is already implemented for the wall behaviour: Review test cases 'TestWallStorage' which tests the (fake) storage of wall messages, 'TestWall' which tests 'PlayerServant's behaviour, but no implementation has been made on the client side, in the 'PlayerProxy'.

Implement test cases (in client project's 'TestWallClient') and proper client side proxy (PlayerProxy) and method dispatching (PlayerInvoker) so clients can interact with the the server. Ensure high test coverage.

Hint: Look for (some of) the TODO tags in the code base.

Requirements:

  1. You must use the fixed method keys (MarshalingKeys.java):
      public static final String ADD_MESSAGE_METHOD_KEY = PLAYER_TYPE_PREFIX + "add-message";
      public static final String UPDATE_MESSAGE_METHOD_KEY = PLAYER_TYPE_PREFIX + "update-message";
      public static final String GET_MESSAGE_LIST_METHOD_KEY = PLAYER_TYPE_PREFIX + "get-message-list";
     

Hand-in:

  1. Submit your source code files for 'PlayerProxy', 'PlayerInvoker', and 'TestWallClient' in BrightSpace. (30 Points).
  2. Once you have your solution code in a Docker image (solved 'skycave-image' in a later iteration), submit it to Crunch, for functional verification. (40 Points).

Evaluation:

Your submission is evaluated against the learning goals and adherence to the submission guidelines. The grading is explained in Grading Guidelines. I will use the grade sheet to evaluate your submission. The score counts towards your final grade.

The functionality will be tested by Crunch, later on.

Learning Goal Assessment parameters
Submission Required artifacts (source files) are all present.
ClientProxy Code Client side proxy code obeys the Broker pattern. The proper MarshalingKeys constant is used. Robert Martin 'Clean Code' properties are generally kept.
Invoker Code PlayerServant Invoker code obeys the Broker pattern. The proper MarshalingKeys constant is used. Robert Martin 'Clean Code' properties are generally kept.
Test Code The test code is simple (no complex constructs, basically only assignments, simple private method calls, very basic for loop). The test code reflects using the TDD principles (Isolated Test, Evident Test, Evident Data, etc.). The production code is 'well' covered by test code (JaCoCo coverage is 'green' for most production code).