Saturday, March 30, 2019

Paper Rock Scissors Game Coding Challenge

Alright I'm trying out this well known coding challenge.

Paper-Rock-Scissors is a hand game usually played by two people, where players simultaneously form one of three shapes with an outstretched hand.

  • The rock beats scissors by blunting it
  • The scissors beat paper by cutting it
  • The paper beats rock by wrapping it

If both players throw the same shape, it is a draw.

My Solution

I'm doing it in Java this time. I try to keep it simple and create the solution as a console application.

Player

Computer and Human for now, both implementing Player interface.

public interface Player {

    String getName();

    Move getMove();
}

Computer player takes in a Random object which simply going to pick a random Move whenever asked.

public class Computer implements Player {

    private final String name;
    private final Random random;

    public Computer(String name, Random random) {
        this.name = name;
        this.random = random;
    }

    @Override
    public String getName() {
        return name;
    }

    @Override
    public Move getMove() {
        MoveSelection[] moveSelections = MoveSelection.values();
        return moveSelections[random.nextInt(moveSelections.length)];
    }
}

Ignore the MoveSelection[] for now, it will become clear when we get to the win / lose / draw logic below.

Human player just has a setter to set Move and a getter to retrieve it back.

public class Human implements Player {

    private final String name;
    private Move move;

    public Human(String name) {
        this.name = name;
    }

    public void setMove(Move move) {
        this.move = move;
    }

    @Override
    public String getName() {
        return name;
    }

    @Override
    public Move getMove() {
        return move;
    }
}

I created two ways to feed human player input into the game:

  • CsvFileInputParser: reads only valid (case insensitive) user input ie. P, R, S, PAPER, ROCK, SCISSORS specified in a csv file and ignores the invalid ones.
  • Interactive from command line: read on for more details

Gamemaster

The purpose of having a gamemaster class is to avoid having too much code in the console application main which is rather difficult to test. Although we can't really fully unit test interactive console application, we still can get some test coverage by doing this.

For some reason, I ended up with three different kinds of gamemaster:

  • HumanVsComputerGamemaster: at first I started with computer vs a very dumb human player whose moves are read in from a csv file. This is what this gamemaster is doing.
  • ComputerVsComputerGamemaster: once I got the simplest working, I started thinking since Player is an interface, we can actually mix and match the players, we can even have a computer player pitches against another computer player. This is how this gamemaster came to be. In fact, it is actually easier to implement than the human vs computer version, so it doesn't take a lot of time to create this.
  • InteractiveHumanVsComputerGamemaster: the interactive gamemaster is built in a similar way as the other two gamemasters. It is imho the hardest to implement as I've never had to read from console using Java, so I had to google it a bit and I decided to use Scanner for this. Note that I'm not sure how to unit test this interactive mode, but I have enough unit tests for the other two gamemasters to be rather confident that it is working as they're all using the same base class ConsoleTwoPlayerGamemaster.

Win / Lose / Draw Logic

The win / lose logic is centralised in MoveSelection enum that implements Move interface

public interface Move {

    List<Move> winsAgainst();

    List<Move> losesTo();
}

I purposely make winsAgainst() and losesTo() a list to sort of model a matrix-like decision making. This might make things a bit complex but I think it's worth it for the extension possibility. So for example if we introduce a new move BLUNT_SCISSORS, that loses to PAPER, ROCK and SCISSORS and wins against nothing, then all we need to do is:

  • add BLUNT_SCISSORS to PAPER, ROCK, SCISSORS's winsAgainst() list
  • add PAPER, ROCK, SCISSORS to BLUNT_SCISSORS's losesTo() list
  • leave BLUNT_SCISSORS's winsAgainst() list empty
and we're pretty much done.

Suppose we introduce yet another move CRUMBLING_ROCK and let's say it loses to ROCK, SCISSORS and BLUNT_SCISSORS (all crumbles the weak rock) and it wins against PAPER (the paper cannot contain all the crumbles), then all we need to do is:

  • add CRUMBLING_ROCK to ROCK, SCISSORS and BLUNT_SCISSORS's winsAgainst() list
  • add CRUMBLING_ROCK to PAPER's losesTo() list
  • add PAPER to CRUMBLING_ROCK's winsAgainst() list

I understand that it probably does not make sense for any player to choose a move with only a few items in the winsAgainst() list as it reduces the chance of winning, however it might make sense if we have a new computer player type for example an easy level computer player.

I also understand that there's a room for win/lose logic inconsistency here everytime a new move is introduced as I have not implemented a validation that every Move should appear in every other Move's winsAgainst() OR losesTo() list, but never in both list. Using the examples I mentioned above, a correct logic should look like this:

public enum MoveSelection implements Move {

    PAPER {

        @Override
        public List<Move> winsAgainst() {
            return List.of(ROCK, BLUNT_SCISSORS);
        }

        @Override
        public List<Move> losesTo() {
            return List.of(SCISSORS, CRUMBLING_ROCK);
        }
    },

    ROCK {

        @Override
        public List<Move> winsAgainst() {
            return List.of(SCISSORS, BLUNT_SCISSORS, CRUMBLING_ROCK);
        }

        @Override
        public List<Move> losesTo() {
            return List.of(PAPER);
        }
    },

    SCISSORS {

        @Override
        public List<Move> winsAgainst() {
            return List.of(PAPER, BLUNT_SCISSORS, CRUMBLING_ROCK);
        }

        @Override
        public List<Move> losesTo() {
            return List.of(ROCK);
        }
    },

    BLUNT_SCISSORS {

        @Override
        public List<Move> winsAgainst() {
            return List.of(CRUMBLING_ROCK);
        }

        @Override
        public List<Move> losesTo() {
            return List.of(PAPER, ROCK, SCISSORS);
        }
    },

    CRUMBLING_ROCK {

        @Override
        public List<Move> winsAgainst() {
            return List.of(PAPER);
        }

        @Override
        public List<Move> losesTo() {
            return List.of(ROCK, SCISSORS, BLUNT_SCISSORS);
        }
    }
}

Here we can see for example that CRUMBLING_ROCK appears in either winsAgainst() OR losesTo() list of each of the other enums, but never on both lists.

TwoPlayerJudge

The TwoPlayerJudge as its name says, takes in two Player's and will determine the winner from the first player's point of view. The TwoPlayerJudge uses the above decision matrix to decide whether the first player is winning, losing or it's a draw. The TwoPlayerJudge has some simple input validation, but as mentioned above, I have not implemented validation on the "matrix". Furthermore, if I do have time to implement this, I probably won't make it the responsibility of the judge.

public TwoPlayerResult judgeFromFirstPlayerPointOfView(Player firstPlayer, Player secondPlayer) {
    Move firstPlayerMove = firstPlayer.getMove();
    validateMove(firstPlayerMove);

    Move secondPlayerMove = secondPlayer.getMove();
    validateMove(secondPlayerMove);

    MoveResult moveResult = MoveResult.DRAW;
    if (firstPlayerMove.losesTo().isEmpty() || firstPlayerMove.winsAgainst().contains(secondPlayerMove)) {
        moveResult = MoveResult.WIN;
    } else if (firstPlayerMove.winsAgainst().isEmpty() || firstPlayerMove.losesTo().contains(secondPlayerMove)) {
        moveResult = MoveResult.LOSE;
    }

    return new TwoPlayerResult(firstPlayerMove, secondPlayerMove, moveResult);
}

public void validateMove(Move move) {
    if (move == null) {
       throw new IllegalStateException("Cannot judge as player does not have move set");
    }

    if (move.winsAgainst().isEmpty() && move.losesTo().isEmpty()) {
       throw new IllegalStateException("Cannot judge as player cannot both wins against nothing and loses to nothing");
    }

    if (move.winsAgainst().equals(move.losesTo())) {
       throw new IllegalStateException("Cannot judge as player cannot both wins against and loses to the same move");
    }
}

TwoPlayerResult is just a container class to store the first and second player move and the move result (WIN, LOSE or DRAW).

public TwoPlayerResult(Move firstPlayerMove, Move secondPlayerMove, MoveResult moveResult) {
    this.firstPlayerMove = firstPlayerMove;
    this.secondPlayerMove = secondPlayerMove;
    this.moveResult = moveResult;
}

Main

This is the entry point to run the console application.

public class Main {

    public static void main(String[] args) {

        Human human = new Human("Hooman");
        Random random = new Random();
        Computer computer = new Computer("Robo", random);
        TwoPlayerJudge judge = new TwoPlayerJudge();

        // The 3 kinds of gamemaster. Comment out the ones you don't want to run.
        // 1) Human vs Computer: human input is read from a csv file under resources
        runHumanVsComputer(human, computer, judge);

        // 2) Computer vs Computer: enter number of rounds and let them fight each other
        runComputerVsComputer(random, computer, judge, 10);

        // 3) Interactive Human vs Computer: human input is read from the console
        runInteractiveHumanVsComputer(human, computer, judge);
    }

    private static void runHumanVsComputer(Human human, Computer computer, TwoPlayerJudge judge) {

        String filePath = Objects.requireNonNull(Main.class.getClassLoader().getResource("HumanMoves.csv")).getFile();
        CsvFileInputParser csvFileParser = new CsvFileInputParser(filePath);
        ConsoleTwoPlayerGamemaster gamemaster = new HumanVsComputerGamemaster(human, csvFileParser.readMoves(), computer, judge);
        System.out.println(gamemaster.startGame());
    }

    private static void runComputerVsComputer(Random random, Computer computer1, TwoPlayerJudge judge, int numberOfRounds) {

        Computer computer2 = new Computer("Robo Wannabe", random);
        ConsoleTwoPlayerGamemaster gamemaster = new ComputerVsComputerGamemaster(computer1, computer2, judge, numberOfRounds);
        System.out.println(gamemaster.startGame());
    }

    private static void runInteractiveHumanVsComputer(Human human, Computer computer, TwoPlayerJudge judge) {

        ConsoleTwoPlayerGamemaster gamemaster = new InteractiveHumanVsComputerGamemaster(human, computer, judge);
        gamemaster.startGame();
    }
}

Source Code

https://github.com/velianarie/PaperRockScissorsGame

Wednesday, October 31, 2018

Feature Toggling Graphql Service with LaunchDarkly

For a project I was working on, we have a graphql service that retrieves clients positions from a SQL database. Under the hood there are some complex SQL stored procedures joining multiple SQL tables and complex data processing before they are finally in a usable shape. We want to move all this complexity to a Hive Big Data project which is more suitable to handle this type of data massaging and have our project consumed a Hive REST API.

The plan is to make sure that once the Hive REST API is live or even during UAT, the consumers of our graphql service do not notice any difference. And if something goes wrong with Hive, we can switch back to good old SQL with very minimal code change. This is where I think LaunchDarkly comes in handy as a fallback scenario. Fortunately I have some prior experience working with it.


The Toy Example

Now to illustrate what I'm dealing with, I have created a toy graphql service. Suppose we have a graphql query that retrieve clients positions defined as:

   type Query {
      positions: [String]
   }

Suppose the result of the query sourced from SQL is:

   {
     "data": {
       "positions": [
         "Sql1",
         "Sql2",
         "Sql3"
       ]
     }
   }

We want to be able to switch to Hive on the fly so that the same query returns:

   {
     "data": {
       "positions": [
         "Hive1",
         "Hive2",
         "Hive3"
       ]
     }
   }

Without feature toggling management, one way to achieve this is to introduce a boolean flag ie. use Hive vs use SQL. The graphql query definition becomes something like this:

   type Query {
      positions(useHive:Boolean!): [String]
   }

This solution isn't always possible because this means we have to change our public API. With LaunchDarkly there's no need to alter our public API, all the code needed for feature toggling happens internally.

Source Code

Using LaunchDarkly

If you're new to LaunchDarkly, see my other post for some background information how to get started.

Create a new feature flag and call it 'position-hive'. When this feature is switched ON, we'll make the positions query returning data from Hive. We can also target a specific set of users who are allowed to use this feature and another set who aren't.

Since I'm using node.js to build this graphql service, we will need LaunchDarkly SDK for Node.js. Install the Node.js SDK with npm:

    npm install ldclient-node --save

We want to make sure that LaunchDarkly client and user are initialised only once when the graphql service started. Once they are initialised, we passed them into the graphql context so we can retrieve these back when needed. Now in the real world there's usually user authentication and authorisation before we can use a service. I'm not going to cover that here, I'm just assuming that's already happening automagically and we can get the user id and name from the graphql request argument.

According to LaunchDarkly SDK documentation, the LaunchDarkly client will emit a 'ready' event when it has been initialized and can serve feature flags. waitForInitialization() function is an alternative to this 'ready' event. If your application uses promises to manage asynchronous operations, which is the case here, this is the recommended way to interact with the SDK. When LaunchDarkly client is ready and can serve feature flags then retrieving a flag setting for a given user can be done this way:

   ldClient.variation(FEATURE_KEY, ldUser, FEATURE_DEFAULT)
     .then(function(featureValue) {
         // application code
     });
The FEATURE_KEY is 'position-hive' and FEATURE_DEFAULT is either true or false. I set it to false in our case as I want our graphql service to use SQL data by default.

Source Code

https://github.com/velianarie/graphql-launchdarkly

Monday, October 29, 2018

Wi-Fi Problem Ubuntu 18.04 USB Boot on MacBook Pro

Always wanted to try Ubuntu but didn't want to risk changing anything in your machine? Well the community has thought about this. They termed it as live run, basically having the whole OS running from a CD, DVD, USB or any portable device you could think of, no installation needed. There are quite a few tutorials explaining how to do this, so I won't bother repeating it here. This tutorial is a good one to get it on any USB stick. I chose USB because that's what I had near me.

Booting from USB works like a charm, everything seems to work except the Wi-Fi! What can a machine do without internet these days? I looked into the Wi-Fi Settings and it's telling me it couldn't find a Wi-Fi adapter.

Unfortunately I don't have an extra machine to google for solution, so I had to use the good old Ethernet cable to connect to internet and that fortunately worked, I don't know what I would do if I had one of those thin laptops. Anyway if you google Ubuntu Wi-Fi problem, it will get you hundreds of hits... seems like this is a well-known problem and my research narrowed it down to missing driver.

To know what you're missing, the first thing you need to do is identify the network card you have. The command in Linux is lspci -vvnn | grep -A 9 Network.

   ubuntu@ubuntu:~$ lspci -vvnn | grep -A 9 Network
   02:00.0 Network controller [0280]: Broadcom Limited BCM4331 802.11a/b/g/n [14e4:4331] (rev 02)
      Subsystem: Apple Inc. AirPort Extreme [106b:00f5]
      ...
      Latency: 0, Cache Line Size: 256 bytes
      Interrupt: pin A routed to IRQ 17
      Region 0: Memory at a0600000 (64-bit, non-prefetchable) [size=16K]
      Capabilities: 
      Kernel driver in use: bcma-pci-bridge
      Kernel modules: bcma
This means I have:
  • The Chip ID: BCM4331
  • The PCI-ID: 14e4:4331
  • Kernel driver in use: bcma-pci-bridge

Fortunately I can see I don't have wl driver, this is easily remedied by installing bcmwl-kernel-source

   sudo apt install bcmwl-kernel-source
My Wi-Fi works afterwards.

If you're not as lucky as I am, knowing your network card is a first step to google for some more solution.

My MacBook Pro spec: macOS High Sierra, 2.9 Ghz Intel Core i7, 8 GB 1600 MHz DDR3

Saturday, July 14, 2018

Pluto Rover Coding Challenge

This is a spin-off of the famous Mars Rover coding assignment.

The Assignment

After NASA’s New Horizon successfully flew past Pluto, they now plan to land a Pluto Rover to further investigate the surface. You are responsible for developing an API that will allow the Rover to move around the planet. As you won’t get a chance to fix your code once it is on board, you are expected to use test driven development.

To simplify navigation, the planet has been divided up into a grid. The rover's position and location is represented by a combination of x and y coordinates and a letter representing one of the four cardinal compass points. An example position might be 0, 0, N, which means the rover is in the bottom left corner and facing North. Assume that the square directly North from (x, y) is (x, y+1).

In order to control a rover, NASA sends a simple string of letters. The only commands you can give the rover are ‘F’,’B’,’L’ and ‘R’

  • Implement commands that move the rover forward/backward (‘F’,’B’). The rover may only move forward/backward by one grid point, and must maintain the same heading.
  • Implement commands that turn the rover left/right (‘L’,’R’). These commands make the rover spin 90 degrees left or right respectively, without moving from its current spot.
  • Implement wrapping from one edge of the grid to another. (Pluto is a sphere after all)
  • Implement obstacle detection before each move to a new square. If a given sequence of commands encounters an obstacle, the rover moves up to the last possible point and reports the obstacle.

Here's an example

  • Let's say that the rover is located at 0,0 facing North on a 100x100 grid.
  • Given the command "FFRFF" would put the rover at 2,2 facing East.

Tips!

  • Don't worry about the structure of the rover. Let the structure evolve as you add more tests.
  • Start simple. For instance you might start with a test that if at 0,0,N with command F, the robots position should now be 0,1,N.
  • Don’t worry about bounds checking until step 3 (implementing wrapping).
  • Don't start up/use the debugger, use your tests to implement the kata. If you find that you run into issues, use your tests to assert on the inner workings of the rover (as opposed to starting the debugger).

My Solution

I started with defining a few things:

  • Enum Command that represents each command (forward, backward, left and right)
  • Enum Orientation that represents the four cardinal directions (north, east, south and west)
  • Class Position that represents the current rover's position in the grid
public enum Command 
{
   Forward,
   Backward,
   Left,
   Right
}
  
public enum Orientation
{
   North,
   South,
   East,
   West
}
    
public class Position
{
   private readonly int x;
   private readonly int y;
   private readonly Orientation orientation;
    
   public Position(int x, int y, Orientation orientation) 
   {
      this.x = x;
      this.y = y;
      this.orientation = orientation;
   }
}

Then I define a static class InputParser that knows how to parse a character from the string of input letters eg. "FFRFF" into a Command. For simplicity, I won't show the code here, it's just a bunch of switch case statements.

Next I define a class Pluto which represents the surface/grid where the rover can move around on. I also think it makes sense to have this class responsible for holding information of the obstacles.

public class Pluto
{
   private readonly int width;
   private readonly int length;
   private readonly List<Tuple<int, int>> obstacles;
     
   public Pluto(int width, int length) 
   {
      this.width = width;
      this.length = length;
      obstacles = new List<Tuple<int, int>>();
   }

   public int Width 
   {
      get { return width; }
   }
   
   public int Length
   {
      get { return length; }
   }
     
   public List<Tuple<int, int>> Obstacles 
   {
      get { return obstacles; }
   }

   public void AddObstacle(Tuple<int, int> obstacle) 
   {
      if (obstacles.Contains(obstacle)) 
      {
         obstacles.Remove(obstacle);
      }
      
      obstacles.Add(obstacle);
   }
}

Finally I define a Rover class that can be initiated as so:

public Rover(int x, int y, Orientation orientation) 
{
   this.x = x;
   this.y = y;
   this.orientation = orientation;
}

This class holds a reference to Pluto with the help of a DeployTo function:

public void DeployTo(Pluto pluto)
{
   this.pluto = pluto;
}

and implements each single move logic as so:

  1. Determine the candidate new position
  2. int candidateX = x;
    int candidateY = y;
    switch (command)
    {
       case Command.Forward:
          if (orientation == Orientation.North) candidateY = candidateY + 1;
          else if (orientation == Orientation.South) candidateY = candidateY - 1;
          else if (orientation == Orientation.East) candidateX = candidateX + 1;
          else candidateX = candidateX - 1;
          break;
       case Command.Backward:
          if (orientation == Orientation.North) candidateY = candidateY - 1;
          else if (orientation == Orientation.South) candidateY = candidateY + 1;
          else if (orientation == Orientation.East) candidateX = candidateX - 1;
          else candidateX = candidateX + 1;
          break;
       case Command.Left:
          if (orientation == Orientation.North) orientation = Orientation.West;
          else if (orientation == Orientation.South) orientation = Orientation.East;
          else if (orientation == Orientation.East) orientation = Orientation.North;
          else orientation = Orientation.South;
          break;
       case Command.Right:
          if (orientation == Orientation.North) orientation = Orientation.East;
          else if (orientation == Orientation.South) orientation = Orientation.West;
          else if (orientation == Orientation.East) orientation = Orientation.South;
          else orientation = Orientation.North;
          break;
       default:
          throw new Exception($"Command '{command}' is not valid.");
    }
  3. Ask Pluto for positions of the obstacles
  4. If there is an obstacle on this candidateX and candidateY position, the Rover just stays on its current x and y position, although it still can change its orientation. If there is no obstacle, candidateX and candidateY is the new position.
  5. If candidateX or candidateY position exceeds Pluto's width and length, adjust candidateX and candidateY accordingly by deducting the width and length.

Things to Improve

If I have more time, here are a few things I want to improve:

  • Replace Tuple<int,int> with Coordinate class
  • Replace Rover(int x, int y, ...) with this Coordinate class
  • if else statements in Move() strikes me as a bit of a code smell (see below my thoughts of how to possibly improve this)
  • Move() function is too long, there's code smell here. Moving logic is tangled with obstacles detection logic. They need to be separated. Think of a change of requirement for example if the Rover is deployed to a planet that's not a wrapped grid, but a torus or an infinite grid.
  • Abstract out Pluto to guard for change of requirement as mentioned before. Maybe call it IPlanet?

My thoughts to improve Command Left and Right

We could use some mathematical tricks like assigning integer number to the Orientation enum. Pick an orientation, assign integer value 1 then go clockwise and assign the next integer value, so something like: East = 1, South = 2, West = 3, North = 4.

Apply the following for Left:

  if (current orientation - 1) < 1, then get the previous orientation as if the enum is a circle 

  else (current orientation - 1) 
  

Example:

  • current orientation: N ==> (4 - 1) = 3 (West)
  • current orientation: S ==> (2 - 1) = 1 (East)
  • current orientation: E ==> (1 - 1) = 0 (the previous orientation is North)
  • current orientation: W ==> (3 - 1) = 2 (South)

Apply similar trick to Right, it is the opposite of Left after all:

  if (current orientation + 1) > 4, then get the next orientation as if the enum is a circle 

  else (current orientation + 1) 
  

Example:

  • current orientation: N ==> (4 + 1) = 5 (the next orientation is East)
  • current orientation: S ==> (2 + 1) = 3 (West)
  • current orientation: E ==> (1 + 1) = 2 (South)
  • current orientation: W ==> (3 + 1) = 4 (North)

This trick would reduce the 8 if else statements to 2 functions.

Note however that I haven't thought about whether the two above pseudocodes would work for different enumeration value ie. if you start with North = 1 for example.

For Command Forward and Backward, we can see the following pattern:

  • Forward North = Backward South
  • Foward South = Backward North
  • Forward East = Backward West
  • Forward West = Backward East

So perhaps we can reduce some of the if else statements based on these facts.

Source Code

https://github.com/velianarie/PlutoRover

Saturday, March 31, 2018

JavaScript Spread Operator and Destructuring

I just found out about this amazing (now rather old) feature of ES6 while googling of ways to clone an object then excluding some of the properties. We basically can do this by combining spread operator with destructuring.

So supposed we have this recipe object and we want to turn this recipe into an alcohol free recipe ie. taking the rum and kirsch out of the recipe.

Output:

   Original recipe: {"egg":45,"butter":60,"sugar":150,"rum":10,"kirsch":10,"milk":120,"flour":150}
   Alcohol free recipe: {"egg":45,"butter":60,"sugar":150,"milk":120,"flour":150}

Saturday, February 3, 2018

Friday, November 17, 2017

[Learning Notes]: TDD for React/Redux in an Isomorphic Application by Hany Elemary

I felt lucky I managed to watch this video before our Safari Books Online subscriptions were discontinued.

History

Hany Elemary begins with a little history of how isomorphic applications came to be. They evolve roughly as follows:
  1. Full-page refresh
    • Thin client (DOM manipulation, form validation, animations)
    • Rich server (routing, views, application logics, storage)
  2. Rich Internet application with AJAX request
    • Rich client (DOM manipulation, form validation, animations + views, application/display logic)
    • Thin server (initial bootstrap, API, storage)
  3. Single Page Application Isomorphic
    • Client (DOM manipulation, form validation, animations)
    • Shared code (routing, views, application/display logic) => runs client-side or server-side based on the request's context
    • Server (API, storage)
To make it easier for beginners to understand the differences between these developments, Hany mentions an analogy of a server in a restaurant and what they do when a customer orders a dish:
  1. Full-page refresh
    • First order: go buy pots pans, groceries, come back, cook & serve
    • Subsequent orders: go buy pots pans, groceries, come back, cook & serve
  2. Rich internet application with AJAX request
    • First order: go buy pots pans, come back, go get groceries, come back, cook & serve
    • Subsequent orders: go get groceries, come back, cook & serve
  3. Single Page Application Isomorphic
    • First order: go buy pots pans, get groceries, come back, cook & serve
    • Subsequent orders: go get groceries, come back, cook & serve

Sequence diagram of isomorphic application

Initial request
  1. User enters URL in browser
  2. Browser requests data from server
  3. Server fetches data from API/Database
  4. API/Database returns data to server eg. JSON
  5. Server generates & serves markup back to browser
  6. Browser render page
Subsequent requests
  1. User navigates in browser
  2. Browser fetches data straight from API/Database
  3. API/Database returns data to browser
  4. Browser renders page

React/Redux Overview

Paradigm shift in front-end development

  • React is library NOT framework
  • React only handles view layer eg. no AJAX, no Promises, no event system, etc. So why do we need it? Well sometimes we only need small composable library rather than a big ecosystem where you only use a tiny fraction of it. Get rid of libraries you don't need and get new libraries when you need it
  • React is component-based architecture: reusable components
  • Web developers often told not to mix HTML with JavaScript because HTML cannot be minified and it also cluttered your components. React uses HTML within JavaScript (JSX). JSX is pre-processor step that compiles your HTML into JavaScript so it could be minified.
  • Virtual DOM and diffing algorithm. Virtual DOM is in-memory representation of the real DOM, it has no knowlege to browser or environment it's run in, it's essentially a data structure. Diffing algorithm makes sure only the difference between virtual and real DOM is committed to the real DOM.

Deterministic view rendering

We know exactly which view is rendered first. In asynchronous function we won't know, whichever request got a response first, it'll be rendered first (race condition) ie. it's non-deterministic.

Event Sourcing

Event sourcing is technique that cares about sequences of events that lead to a specific outcome rather than the outcome itself. We want to store all events that occurred through our system as opposed to the outcomes that have happened. Characteristic: no update, no delete, it's an append-only log of events. In accounting terms: general ledger. Your book of records becomes stream of events, your source of truth. Advantages:
  • We have audit trail of all events that have happened in our system
  • You can replay a sequence of events to bring the application to a very specific state

Redux was inspired by Event Sourcing

  • Single source of truth: Redux Store
  • State is read-only/immutable. We only appending things, we're not updating or deleting things
  • Changes (ie. actions not events!) are made with pure functions (reducers). Reducers: given the same parameters will always output the same result, no side effect, no network operation, etc

Developer's workflow

  1. Write action / action creator
  2. Write component reducer to return the new state
  3. Determine the shape of your state / data - root reducer
  4. Write component that dispatches your action
  5. Connect component to store to subscribe to changes

TDD with React/Redux - Simple Components

Test pyramid

  1. UI
  2. Driven through the browser. Ensure the app is healthy in very high level, only test business critical features that represent overall workflows. Tend to be very slow and very expensive to maintain.
  3. Service (service level test, contract test)
  4. Expecting specific response for API given specific request. If API changes, test should fail. Protecting your application from changes in downstream dependencies or if your application is dependent on an API that you don't own.
  5. Unit
  6. Responsible for example display logic, behaviour, integration between components. Should be very fast and inexpensive to maintain.

Testing framework

  • Mocha - Test framework: assertions, expectations
  • Sinonjs - Stubbing framework: fake and spy on function calls
  • Mochawesome - Reporting: HTML reports that generate with each run, important to integrate with CI/CD pipeline
  • Enzyme - React test utility: rendering mechanism, access to components instances and children
  • Istanbul - Reporting: code coverage

TDD with React/Redux - Async Operation

Redux Saga

  • A library that aims to make side-effects easier and better.
  • What is side effects? network / asynchronous operations like fetching data. We can't guarantee that the same request will always be successful.
  • Reads like synchronous code. Think of Redux Saga as separate thread in your application.
  • ES6 generators makes an asynchronous code appears synchronous.
  • Redux Saga is a middleware. (PS: middleware is some code you can put between the framework receiving a request and the framework generating a response eg. logging)

Testing frameworks

  • Nock - HTTP mocking: useful for testing components integration with APIs, E2E tests
  • Mountebank - HTTP stubbing over the wire: useful for testing components integration with APIs, E2E tests, simulating failure modes.
  • Mountebank allows you to create an actual server and then mock the HTTP request and response. You can do pattern matching ie. if I see this pattern in the request then return this particular response. Another feature is latency ie. this request will return this response after n seconds. This can be used to simulate loading in your application.